From 19977655c4d8c56df221f9c13080bba2846d1079 Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Wed, 1 Mar 2023 15:55:34 -0500 Subject: [PATCH 01/12] End of Mar. 1 commit - Start adding motor protections - add motor config variables to config file - remove magic numbers in TesseractFacade --- dependency-reduced-pom.xml | 2 +- pom.xml | 2 +- runScript.sh | 2 +- src/main/java/org/baxter/disco/ocr/Cli.java | 165 +++++------- .../org/baxter/disco/ocr/ConfigFacade.java | 57 +++- .../org/baxter/disco/ocr/MovementFacade.java | 245 ++++++++++-------- .../org/baxter/disco/ocr/TesseractFacade.java | 40 ++- 7 files changed, 300 insertions(+), 213 deletions(-) diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index 6f2932f..bb421ef 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -4,7 +4,7 @@ org.baxter.disco ocr Disco OCR Accuracy Over Life Testing - 4.3.4 + 4.3.5 Testing Discos for long-term accuracy, using automated optical character recognition. Baxter International diff --git a/pom.xml b/pom.xml index d081594..af94a17 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.baxter.disco ocr - 4.3.4 + 4.3.5 jar Disco OCR Accuracy Over Life Testing Testing Discos for long-term accuracy, using automated optical character recognition. diff --git a/runScript.sh b/runScript.sh index e69739b..b4ef9f6 100644 --- a/runScript.sh +++ b/runScript.sh @@ -1,2 +1,2 @@ #! /usr/bin/env sh -sudo java -jar discoTesting-4.3.4.jar 2>/dev/null +sudo java -jar discoTesting-4.3.5.jar 2>/dev/null diff --git a/src/main/java/org/baxter/disco/ocr/Cli.java b/src/main/java/org/baxter/disco/ocr/Cli.java index d633d68..37badb7 100644 --- a/src/main/java/org/baxter/disco/ocr/Cli.java +++ b/src/main/java/org/baxter/disco/ocr/Cli.java @@ -25,7 +25,7 @@ public class Cli /** * Complete build version number */ - private static final String version = "4.3.4"; + private static final String version = "4.3.5"; /** * Currently saved iteration count. @@ -58,22 +58,17 @@ public class Cli /** * Number of options currently available in the movement sub-menu. */ - private static final int movementMenuOptionCount = 4; + private static final int movementMenuOptionCount = 3; /** * Number of options currently available in the camera configuration sub-menu. */ - private static final int cameraMenuOptionCount = 10; + private static final int cameraMenuOptionCount = 6; /** * Lock object, used for temporary interruption of {@link #runTests()} */ - private static Lock LOCK = new ReentrantLock(); - - /** - * Instance of {@link MovementFacade} for controlling the fixture. - */ - private static MovementFacade fixture; + public static final Lock LOCK = new ReentrantLock(); //private static Thread safeThread; @@ -91,112 +86,80 @@ public class Cli ErrorLogging.logError("Version: " + version); ErrorLogging.logError("========================"); try{ - //Create scanner for user input from the console inputScanner = new Scanner(System.in); - //Initialise the fixture, start monitor thread - fixture = new MovementFacade(LOCK); - - //Initialise the config ConfigFacade.init(); - //Create the user input value int userInput = 0; - //Main menu loop + boolean fixtureTested = false; + do { - //Show the main menu, wait for user input printMainMenu(); userInput = (int)inputFiltering(inputScanner.nextLine()); - //Perform action based on user input switch (userInput) { case 1: - //Test fixture movement, modify fixture values as necessary testMovement(); + fixtureTested = true; break; case 2: - //Warn user that starting cameras will take a moment. println("Setting up cameras..."); println("This may take a moment..."); configureCameras(); - //Set that cameras are successfully configured, to mute runTests warning camerasConfigured = true; break; case 3: - //Set serials of the DUTs setDUTSerials(); break; case 4: - //Change the number of iterations to run the tests setIterationCount(); break; case 5: - //Set cameras to use in testing setActiveCameras(); break; case 6: - //Warn user that cameras haven't been set up, if they haven't been set up - //Won't warn user if config was imported successfully + if(!fixtureTested) + { + prompt("You have not tested the fixture's movement configuration! Are you sure you would like to continue? (y/N): "); + String input = inputScanner.nextLine().toLowerCase().trim(); + if(input.isBlank() || input.charAt(0) != 'y') break; + else + ErrorLogging.logError("DEBUG: Potential for error: Untested fixture movement config."); + } if(!camerasConfigured) { prompt("You have not configured the cameras yet! Are you sure you would like to continue? (y/N): "); - String input = inputScanner.nextLine().toLowerCase(); - if( input.isBlank()) - { - break; - } - else if (input.charAt(0) != 'y' ) - { - break; - } - //Save in the logs that cameras may not have been configured. + String input = inputScanner.nextLine().toLowerCase().trim(); + if( input.isBlank() || input.charAt(0) != 'y' ) break; else - { - ErrorLogging.logError("WARNING! - Potential for error: Un-initialised cameras."); - } + ErrorLogging.logError("DEBUG: Potential for error: Un-initialised cameras."); } - //If there's an unset camera serial, prompt the user to go back and set that up + serialsSet = true; for(String cameraName : OpenCVFacade.getCameraNames()) { if(ConfigFacade.getValue(cameraName,ConfigProperties.ACTIVE) != 0 && ConfigFacade.getSerial(cameraName) == null ) - { serialsSet = false; - break; - } - else serialsSet = true; } if(!serialsSet) { prompt("You have not set the serial numbers for your DUTs yet! Are you sure you would like to continue? (y/N): "); - String input = inputScanner.nextLine().toLowerCase(); - if( input.isBlank()) - { - break; - } - else if (input.charAt(0) != 'y' ) - { - break; - } + String input = inputScanner.nextLine().toLowerCase().trim(); + if( input.isBlank() || input.charAt(0) != 'y' ) break; else - { - ErrorLogging.logError("WARNING! - Potential for error: Un-initialised DUT Serial numbers."); - } + ErrorLogging.logError("DEBUG: Potential for error: Un-initialised DUT Serial numbers."); } - //Run tests for the given number of iterations runTests(); break; case 7: - //Show help menu printHelp(); break; case 8: - //Leave the menu break; default: //Input handling already done by inputFiltering() @@ -253,7 +216,6 @@ public class Cli "\n\tdirections, to check range"+ "\n\tof motion." + "\n\tAvailable variables to change:"+ - "\n\t\tPWM Duty Cycle"+ "\n\t\tPWM Frequency"+ "\n\t\tMotor Time-out"); println("----------------------------------------"); @@ -321,14 +283,12 @@ public class Cli println("===================================="); println("Movement Menu:"); println("------------------------------------"); - println("Current Duty Cycle: " + fixture.getDutyCycle()); - println("Current Frequency: " + fixture.getFrequency()); - println("Current Motor Time-out: " + fixture.getTimeout()); + println("Current Frequency: " + MovementFacade.getUserFrequency() + "KHz"); + println("Current Motor Time-out: " + MovementFacade.getTimeout()); println("------------------------------------"); - println("1. Change Duty Cycle"); - println("2. Change Frequency"); - println("3. Change Motor Time-out"); - println("4. Exit"); + println("1. Change Frequency"); + println("2. Change Motor Time-out"); + println("3. Exit"); println("===================================="); } @@ -408,7 +368,7 @@ public class Cli println("Will the image be thresholded? " + thresholdImage); println("Tesseract parsed value for camera " + cameraName + ": " + tesseractValue); println("------------------------------------"); - println("1. Change Crop Point"); + println("1. Change Crop Region"); println("2. Change Composite Frame Count"); println("3. Change Threshold Value"); println("4. Toggle crop"); @@ -428,49 +388,64 @@ public class Cli do { println("Testing movement..."); - fixture.testMotions(); + MovementFacade.reset(); + MovementFacade.FinalState downwardMove = MovementFacade.goDown(); + switch(downwardMove) + { + case UNSAFE: + ErrorLogging.logError("Movement warning!!! - Time-out too long, motor does not slow down before hitting limit switch. Consider setting this value to be lower."); + break; + case FAILED: + ErrorLogging.logError("Movement warning!!! - Fixture did not hit lower limit switch. Consider changing frequency."); + ErrorLogging.logError("Fixture movement configuration: Downward movement missed."); + break; + case SAFE: + MovementFacade.FinalState returnMove = MovementFacade.goUp(); + switch(returnMove) + { + case UNSAFE: + ErrorLogging.logError("Movement warning!!! - Time-out too long, motor does not slow down before hitting limit switch. Consider setting this value to be lower."); + break; + case FAILED: + ErrorLogging.logError("Movement warning!!! - Fixture did not hit upper limit switch. Consider changing frequency."); + ErrorLogging.logError("Fixture movement configuration: Return movement missed."); + break; + case SAFE: + } + } printMovementMenu(); userInput = (int)inputFiltering(inputScanner.nextLine()); switch (userInput) { /* * Menu options: - * 1. Change Duty Cycle - * 2. Change Frequency - * 3. Change Motor Time-out - * 4. Exit + * 1. Change Frequency + * 2. Change Motor Time-out + * 3. Exit */ case 1: - prompt("Input the desired duty cycle value: "); - int newDutyCycle = (int)inputFiltering(inputScanner.nextLine()); - if (newDutyCycle != -1) - { - fixture.setDutyCycle(newDutyCycle); - break; - } - case 2: - prompt("Input the desired frequency value: "); + prompt("Input the desired frequency value (in KHz): "); int newFrequency = (int)inputFiltering(inputScanner.nextLine()); if (newFrequency != -1) { - fixture.setFrequency(newFrequency); + MovementFacade.setFrequency(newFrequency); break; } - case 3: + case 2: prompt("Input the desired time-out (in seconds): "); double newTimeout = inputFiltering(inputScanner.nextLine()); if (newTimeout != -1) { - fixture.setTimeout(newTimeout); + MovementFacade.setTimeout(newTimeout); break; } - case 4: + case 3: break; default: ErrorLogging.logError("User Input Error!!! - Invalid input."); } } - while(userInput != 4); + while(userInput != 3); } /** @@ -481,7 +456,7 @@ public class Cli List cameraList = new ArrayList<>(OpenCVFacade.getCameraNames()); //Always wake the camera, to ensure that the image is useful - fixture.iterationMovement(true); + MovementFacade.iterationMovement(true); double tesseractValue = 0.0; //Main camera config loop @@ -509,9 +484,9 @@ public class Cli do { //Press button twice, to make sure the DUT is awake - fixture.pressButton(); + MovementFacade.pressButton(); try{ Thread.sleep(2000); } catch(Exception e){ ErrorLogging.logError(e); } - fixture.pressButton(); + MovementFacade.pressButton(); try{ Thread.sleep(2000); } catch(Exception e){ ErrorLogging.logError(e); } //Show image @@ -707,7 +682,7 @@ public class Cli //Wake the device, then wait to ensure they're awake before continuing ErrorLogging.logError("DEBUG: Waking devices..."); - fixture.pressButton(); + MovementFacade.pressButton(); try{ Thread.sleep(2000); } catch(Exception e){ ErrorLogging.logError(e); } //Create final maps for result images, result values, and camera names @@ -739,7 +714,7 @@ public class Cli fail = false; //Move the fixture for one iteration, with whether or not the DUTs need to be primed while(!LOCK.tryLock()) {} - fixture.iterationMovement(prime); + MovementFacade.iterationMovement(prime); LOCK.unlock(); //Wait for the DUT to display an image @@ -778,10 +753,10 @@ public class Cli result >= 100 || result == Double.NEGATIVE_INFINITY) { - fixture.goUp(); + MovementFacade.goUp(); try{ Thread.sleep(20000); } catch(Exception e){ ErrorLogging.logError(e); } - fixture.pressButton(); + MovementFacade.pressButton(); fail = true; break; } @@ -819,7 +794,7 @@ public class Cli { ErrorLogging.logError("DEBUG: PROGRAM CLOSING."); if(inputScanner != null) inputScanner.close(); - fixture.closeGPIO(); + MovementFacade.closeGPIO(); ErrorLogging.logError("DEBUG: END OF PROGRAM."); ErrorLogging.closeLogs(); println("The program has exited successfully. Please press Ctrl-c to return to the terminal prompt."); diff --git a/src/main/java/org/baxter/disco/ocr/ConfigFacade.java b/src/main/java/org/baxter/disco/ocr/ConfigFacade.java index dc0a79c..17337db 100644 --- a/src/main/java/org/baxter/disco/ocr/ConfigFacade.java +++ b/src/main/java/org/baxter/disco/ocr/ConfigFacade.java @@ -14,6 +14,7 @@ import java.util.ArrayList; //Apache Commons Configuration imports import org.apache.commons.configuration2.INIConfiguration; +import org.apache.commons.configuration2.SubnodeConfiguration; import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder; import org.apache.commons.configuration2.builder.fluent.Parameters; @@ -59,6 +60,15 @@ public class ConfigFacade */ private static final Map DUT_SERIALS = new HashMap<>(); + private static final String MOVEMENT_SECTION = "Fixture"; + private static int MOVEMENT_FREQ; + + private static final int MOVEMENT_DEFAULT_FREQ = 70000; + + private static double MOVEMENT_TIME_OUT; + + private static final double MOVEMENT_DEFAULT_TIME_OUT = 2.5; + /** * Builder for the main Configuration object. * @@ -76,6 +86,7 @@ public class ConfigFacade //This block will ALWAYS run first. static { + //Get config values ErrorLogging.logError("Starting configuration setup..."); //Give CONFIG_STORE an intentionally bad value CONFIG_STORE = null; @@ -140,6 +151,31 @@ public class ConfigFacade //Autosave the config CONFIG_BUILDER.setAutoSave(true); } + + /** + * + */ + public static double getFixtureValue(FixtureValues value) + { + switch (value) + { + case FREQUENCY: return (double)MOVEMENT_FREQ; + case TIMEOUT: return MOVEMENT_TIME_OUT; + default: return -1.0; + } + } + + public static boolean setFixtureValue(FixtureValues type, double value) + { + boolean output = true; + switch(type) + { + case FREQUENCY: MOVEMENT_FREQ = (int)value; + case TIMEOUT: MOVEMENT_TIME_OUT = value; + default: + } + return output; + } /** * Get a given config value. * All values are stored as doubles. @@ -169,7 +205,6 @@ public class ConfigFacade Map cameraConfig = configMap.get(cameraName); output = cameraConfig.get(property); //Debug logger. - //NOTE THAT THIS BREAKS TUI MENUS, AS OF ErrorLogging 1.1.0 //ErrorLogging.logError("DEBUG: getValue - return value: " + cameraName // + "/" + property.getConfig() + " = " + output); return output; @@ -329,6 +364,9 @@ public class ConfigFacade configMap.put(camera,cameraConfig); } + CONFIG_STORE.setProperty(MOVEMENT_SECTION + "." + "frequency", MOVEMENT_DEFAULT_FREQ); + CONFIG_STORE.setProperty(MOVEMENT_SECTION + "." + "timeout", MOVEMENT_DEFAULT_TIME_OUT); + //Save out to the file try { @@ -373,6 +411,9 @@ public class ConfigFacade } } + CONFIG_STORE.setProperty(MOVEMENT_SECTION + "." + "frequency", MOVEMENT_FREQ); + CONFIG_STORE.setProperty(MOVEMENT_SECTION + "." + "timeout", MOVEMENT_TIME_OUT); + //Save to the file try { @@ -430,6 +471,17 @@ public class ConfigFacade //Iterate over the imported object, saving the file's config values to the map Set configSections = CONFIG_STORE.getSections(); + if(configSections.remove(MOVEMENT_SECTION)) + { + SubnodeConfiguration fixtureConfiguration = CONFIG_STORE.getSection(MOVEMENT_SECTION); + MOVEMENT_FREQ = fixtureConfiguration.getInt("frequency"); + MOVEMENT_TIME_OUT = fixtureConfiguration.getDouble("timeout"); + } + else + { + MOVEMENT_FREQ = MOVEMENT_DEFAULT_FREQ; + MOVEMENT_TIME_OUT = MOVEMENT_DEFAULT_TIME_OUT; + } for(String sectionName : configSections) { Map savedSection = new HashMap<>(); @@ -482,4 +534,7 @@ public class ConfigFacade * @return true if loaded successfully, otherwise false */ public static boolean loadConfig() { return loadConfig(configFileLocation); } + + public enum FixtureValues + { FREQUENCY, TIMEOUT; } } diff --git a/src/main/java/org/baxter/disco/ocr/MovementFacade.java b/src/main/java/org/baxter/disco/ocr/MovementFacade.java index c959c9e..279af18 100644 --- a/src/main/java/org/baxter/disco/ocr/MovementFacade.java +++ b/src/main/java/org/baxter/disco/ocr/MovementFacade.java @@ -1,8 +1,5 @@ package org.baxter.disco.ocr; -//Standard imports -import java.util.concurrent.locks.Lock; - //Pi4J imports import com.pi4j.Pi4J; import com.pi4j.context.Context; @@ -23,7 +20,7 @@ import com.pi4j.io.pwm.PwmType; * Currently missing Run switch compatibility. * * @author Blizzard Finnegan - * @version 2.3.0, 27 Feb. 2023 + * @version 2.4.0, 01 Mar. 2023 */ public class MovementFacade { @@ -34,51 +31,71 @@ public class MovementFacade * @param LOCK A Lock object, used for interactions with * the physical lock switch on the fixture. */ - public MovementFacade(Lock LOCK) - { - //ErrorLogging.logError("DEBUG: Starting lock thread..."); - runSwitchThread = new Thread(() -> - { - boolean unlock = false; - while(!exit) - { - if(runSwitch.isOn()) - { - ErrorLogging.logError("Run switch turned off!"); - while(!LOCK.tryLock()) - {} - unlock = true; - } - else - { - //ErrorLogging.logError("Run switch on!"); - if(unlock) - { LOCK.unlock(); unlock = false; } - } - //try{ Thread.sleep(100); } catch(Exception e) { ErrorLogging.logError(e); } - } - }, "Run switch monitor."); - runSwitchThread.start(); - //ErrorLogging.logError("DEBUG: Lock thread started!"); - } + //public MovementFacade(Lock LOCK) + //{ + // //ErrorLogging.logError("DEBUG: Starting lock thread..."); + // runSwitchThread = new Thread(() -> + // { + // boolean unlock = false; + // while(!exit) + // { + // if(runSwitch.isOn()) + // { + // ErrorLogging.logError("Run switch turned off!"); + // while(!LOCK.tryLock()) + // {} + // unlock = true; + // } + // else + // { + // //ErrorLogging.logError("Run switch on!"); + // if(unlock) + // { LOCK.unlock(); unlock = false; } + // } + // //try{ Thread.sleep(100); } catch(Exception e) { ErrorLogging.logError(e); } + // } + // }, "Run switch monitor."); + // runSwitchThread.start(); + // //ErrorLogging.logError("DEBUG: Lock thread started!"); + //} private static Thread runSwitchThread; + /** + * Internal PWM Frequency + */ + private static int FREQUENCY; + + /** + * Conversion factor for freq to FREQUENCY + */ + private static final int FREQUENCY_UNITS = 1000; + + /** + * Max allowable frequency by current fixture design. + */ + private static final int MAX_FREQUENCY = 175000; + + /** + * Minimum allowed frequency; also used for reset travels. + */ + private static final int MIN_FREQUENCY = 25000; + //Externally Available Variables /** - * PWM Frequency + * Human-readable frequency */ - private static int FREQUENCY = 75000; + private static double freq; /** * PWM Duty Cycle */ - private static int DUTY_CYCLE = 50; + private static final int DUTY_CYCLE = 50; /** * Number of seconds to wait before timing out a fixture movement. */ - private static double TIME_OUT = 2.5; + private static double TIME_OUT; //PWM Addresses //All addresses are in BCM format. @@ -121,7 +138,7 @@ public class MovementFacade /** * How many milliseconds to wait before polling the GPIO */ - private static final int POLL_WAIT = 20; + private static final int POLL_WAIT = 10; /** * How many times to poll the GPIO during a movement call. @@ -192,6 +209,35 @@ public class MovementFacade static { + FREQUENCY = (int)ConfigFacade.getFixtureValue(ConfigFacade.FixtureValues.FREQUENCY); + freq = FREQUENCY / FREQUENCY_UNITS; + TIME_OUT = (int)ConfigFacade.getFixtureValue(ConfigFacade.FixtureValues.TIMEOUT); + + //ErrorLogging.logError("DEBUG: Starting lock thread..."); + runSwitchThread = new Thread(() -> + { + boolean unlock = false; + while(!exit) + { + if(runSwitch.isOn()) + { + ErrorLogging.logError("Run switch turned off!"); + while(!Cli.LOCK.tryLock()) + {} + unlock = true; + } + else + { + //ErrorLogging.logError("Run switch on!"); + if(unlock) + { Cli.LOCK.unlock(); unlock = false; } + } + //try{ Thread.sleep(100); } catch(Exception e) { ErrorLogging.logError(e); } + } + }, "Run switch monitor."); + runSwitchThread.start(); + //ErrorLogging.logError("DEBUG: Lock thread started!"); + //Initialise Pi4J pi4j = Pi4J.newAutoContext(); @@ -306,35 +352,12 @@ public class MovementFacade } - /** - * Setter for the fixture's PWM duty cycle. - * - * @param newDutyCycle The new duty cycle to be set by the user. - * - * @return True if the value was set successfully; otherwise false. - */ - public boolean setDutyCycle(int newDutyCycle) - { - boolean output = false; - if(newDutyCycle < 0) - { - ErrorLogging.logError("Movement error!!! - Invalid DutyCycle input."); - } - else - { - DUTY_CYCLE = newDutyCycle; - pwm.on(DUTY_CYCLE, FREQUENCY); - output = true; - } - return output; - } - /** * Getter for the fixture's PWM duty cycle. * * @return The current DutyCycle. */ - public int getDutyCycle() { return DUTY_CYCLE; } + public static int getDutyCycle() { return DUTY_CYCLE; } /** * Setter for the fixture's time to give up on a movement. @@ -343,7 +366,7 @@ public class MovementFacade * * @return True if the value was set successfully; otherwise false. */ - public boolean setTimeout(double newTimeout) + public static boolean setTimeout(double newTimeout) { boolean output = false; if(newTimeout < 0) @@ -353,6 +376,7 @@ public class MovementFacade else { TIME_OUT = newTimeout; + ConfigFacade.setFixtureValue(ConfigFacade.FixtureValues.TIMEOUT, newTimeout); POLL_COUNT = TIME_OUT * ( 1000 / POLL_WAIT ); output = true; } @@ -364,7 +388,7 @@ public class MovementFacade * * @return The current timeout. */ - public double getTimeout() { return TIME_OUT; } + public static double getTimeout() { return TIME_OUT; } /** * Setter for the fixture's PWM frequency. @@ -373,16 +397,22 @@ public class MovementFacade * * @return True if the value was set successfully; otherwise false. */ - public boolean setFrequency(int newFrequency) + public static boolean setFrequency(double newFrequency) { boolean output = false; if(newFrequency < 0) { - ErrorLogging.logError("Movement error!!! - Invalid frequency input."); + ErrorLogging.logError("Movement error! - Invalid frequency input."); + } + else if(newFrequency > MAX_FREQUENCY) + { + ErrorLogging.logError("Movement warning!!! - Value above maximum allowed."); } else { - FREQUENCY = newFrequency; + freq = newFrequency; + ConfigFacade.setFixtureValue(ConfigFacade.FixtureValues.FREQUENCY, newFrequency); + FREQUENCY = (int)(freq * FREQUENCY_UNITS); pwm.on(DUTY_CYCLE, FREQUENCY); output = true; } @@ -390,24 +420,33 @@ public class MovementFacade } /** - * Getter for the fixture's PWM frequency. + * Getter for the fixture's PWM frequency, in hertz. * * @return The current PWM frequency. */ - public int getFrequency() { return FREQUENCY; } + public static int getFrequency() { return FREQUENCY; } + + /** + * Getter for the fixture's PWM frequency, in KHz. + * + * @return The current PWM frequency. + */ + public static double getUserFrequency() { return freq; } /** * Internal function to send the fixture to a given limit switch. * + * Detects if the limit switch is active before activating motor. + * * Motor slows down after timeout is halfway through, to protect hardware. * * @param moveUp Whether to send the fixture up or down. (True = up, False = down) * @param timeout How long (in seconds) to wait before timing out. * @return true if movement was successful; otherwise false */ - private boolean gotoLimit(boolean moveUp, double timeout) + private static FinalState gotoLimit(boolean moveUp, double timeout) { - boolean output = false; + FinalState output = FinalState.FAILED; DigitalInput limitSense; if(moveUp) { @@ -422,32 +461,50 @@ public class MovementFacade ErrorLogging.logError("DEBUG: Sending fixture down..."); } - double mostlyThere = (POLL_COUNT * 2) / 3; + if(limitSense.isHigh()) return FinalState.SAFE; + + double mostlyThere = (POLL_COUNT * 1) / 2; int slowerSpeed = FREQUENCY / 4; motorEnable.on(); for(int i = 0; i < (POLL_COUNT);i++) { - try{ Thread.sleep(POLL_WAIT); } catch(Exception e) {ErrorLogging.logError(e);}; - output = limitSense.isHigh(); - if(output) break; + try{ Thread.sleep(POLL_WAIT); } + catch(Exception e) {ErrorLogging.logError(e);}; + + if(limitSense.isHigh()) + { + output = ( (i >= mostlyThere) ? FinalState.SAFE : FinalState.UNSAFE); + break; + } else if(i >= mostlyThere) - { pwm.on(DUTY_CYCLE, slowerSpeed); continue; } + { + pwm.on(DUTY_CYCLE, slowerSpeed); + continue; + } } - if(output == false) + + if(output == FinalState.FAILED) ErrorLogging.logError("FIXTURE MOVEMENT ERROR! - Motor movement timed out!"); motorEnable.off(); pwm.on(DUTY_CYCLE, FREQUENCY); return output; } + public static void reset() + { + pwm.on(DUTY_CYCLE, MIN_FREQUENCY); + goUp(Double.POSITIVE_INFINITY); + pwm.on(DUTY_CYCLE, FREQUENCY); + } + /** * Send the fixture to the lower limit switch. * * @param timeout How long (in seconds) to wait before timing out. * @return true if movement was successful; otherwise false */ - public boolean goDown(double timeout) { return gotoLimit(false, timeout); } + public static FinalState goDown(double timeout) { return gotoLimit(false, timeout); } /** * Send the fixture to the upper limit switch. @@ -455,7 +512,7 @@ public class MovementFacade * @param timeout How long (in seconds) to wait before timing out. * @return true if movement was successful; otherwise false */ - public boolean goUp(double timeout) { return gotoLimit(true, timeout); } + public static FinalState goUp(double timeout) { return gotoLimit(true, timeout); } /** * Send the fixture to the lower limit switch. @@ -463,7 +520,7 @@ public class MovementFacade * * @return true if movement was successful; otherwise false */ - public boolean goDown() { return goDown(TIME_OUT); } + public static FinalState goDown() { return goDown(TIME_OUT); } /** * Send the fixture to the upper limit switch. @@ -471,12 +528,12 @@ public class MovementFacade * * @return true if movement was successful; otherwise false */ - public boolean goUp() { return goUp(TIME_OUT); } + public static FinalState goUp() { return goUp(TIME_OUT); } /** * Extends the piston for 1 second, pushing the button on the DUT. */ - public void pressButton() + public static void pressButton() { ErrorLogging.logError("DEBUG: Pressing button..."); pistonActivate.on(); @@ -488,9 +545,9 @@ public class MovementFacade /** * Closes connections to all GPIO pins. */ - public void closeGPIO() + public static void closeGPIO() { - goUp(); + reset(); if(runSwitchThread.isAlive()) { exit = true; @@ -499,34 +556,20 @@ public class MovementFacade pi4j.shutdown(); } - /** - * Tests all available motions of the fixture. - * - * @return True if all movements worked properly; otherwise False - */ - public boolean testMotions() - { - boolean output = goUp(); - if(!output) return output; - pressButton(); - output = goDown(); - if(!output) return output; - pressButton(); - output = goUp(); - return output; - } - /** * Function to move the fixture once for an iteration. * * @param prime Whether or not to wake up the DUT */ - public void iterationMovement(boolean prime) + public static void iterationMovement(boolean prime) { goUp(); - //if(prime) pressButton(); + if(prime) pressButton(); goDown(); try{ Thread.sleep(100); } catch(Exception e){ ErrorLogging.logError(e); } pressButton(); } + + public enum FinalState + { UNSAFE, SAFE, FAILED; } } diff --git a/src/main/java/org/baxter/disco/ocr/TesseractFacade.java b/src/main/java/org/baxter/disco/ocr/TesseractFacade.java index 991ae45..a4b382d 100644 --- a/src/main/java/org/baxter/disco/ocr/TesseractFacade.java +++ b/src/main/java/org/baxter/disco/ocr/TesseractFacade.java @@ -20,7 +20,7 @@ import org.bytedeco.tesseract.TessBaseAPI; * information for this specific testing aparatus. * * @author Blizzard Finnegan - * @version 2.2.0, 27 Feb. 2023 + * @version 2.2.1, 27 Feb. 2023 */ public class TesseractFacade { @@ -30,21 +30,35 @@ public class TesseractFacade */ private static TessBaseAPI api; + /** + * OCR engine mode. + * + * From https://ai-facets.org/tesseract-ocr-best-practices/: + * 0: Legacy engine only + * 1: Neural nets Long Short-Term Memory (LSTM) engine only. This form of neural network has feedback, as well as feedforward within the design, allowing the neural network to learn from itself. + * 2: Legacy + LSTM engines + * 3: Default, based on what is available + * + * As I didn't write the training data, and don't actually know what kind of network the training set requires, this value is set to default. + */ + private static final int OCR_ENGINE_MODE = 3; + + /** + * OCR language name, or training data filename. + */ + private static final String OCR_LANGUAGE = "Pro6_temp_test"; + + /** + * Location on the file system that the OCR languages are stored. + * + * This value requires that the folder "tessdata" be in the same location as your current working directory. + */ + private static final String OCR_LANGUAGE_LOCATION = "tessdata"; + static { - //Initialise the Tesseract API api = new TessBaseAPI(); - - //Magic number below. - //The seemingly random 3 in the following line - //is used to define the OCR Engine mode. - //This mode autoselects the OCR Engine, based on - //available hardware. - //This line also sets the location of the language - //files, and declares the language as "Pro6_temp_test". - //Considering changing this to be more understandable, - //but potential consequences are unclear. - api.Init("tessdata", "Pro6_temp_test", 3); + api.Init(OCR_LANGUAGE_LOCATION, OCR_LANGUAGE, OCR_ENGINE_MODE); } /** -- 2.47.2 From 31cba9e0e9341f2f210dd325e9e25d0f3741897a Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Wed, 1 Mar 2023 16:02:24 -0500 Subject: [PATCH 02/12] UNTESTED : Fix gotoLimit error Timeout override now works properly --- src/main/java/org/baxter/disco/ocr/MovementFacade.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/baxter/disco/ocr/MovementFacade.java b/src/main/java/org/baxter/disco/ocr/MovementFacade.java index 279af18..becdd8e 100644 --- a/src/main/java/org/baxter/disco/ocr/MovementFacade.java +++ b/src/main/java/org/baxter/disco/ocr/MovementFacade.java @@ -141,10 +141,9 @@ public class MovementFacade private static final int POLL_WAIT = 10; /** - * How many times to poll the GPIO during a movement call. - * The 1000/POLL_WAIT in this definition is converting poll-times to polls-per-second. + * Multiply the time-out value by this value to get the number of polls to make. */ - private static double POLL_COUNT = TIME_OUT * (1000 / POLL_WAIT); + private static final int TIME_CONVERSION = 1000 / POLL_WAIT; //Pi GPIO pin objects @@ -377,7 +376,6 @@ public class MovementFacade { TIME_OUT = newTimeout; ConfigFacade.setFixtureValue(ConfigFacade.FixtureValues.TIMEOUT, newTimeout); - POLL_COUNT = TIME_OUT * ( 1000 / POLL_WAIT ); output = true; } return output; @@ -463,6 +461,7 @@ public class MovementFacade if(limitSense.isHigh()) return FinalState.SAFE; + double POLL_COUNT = timeout * TIME_CONVERSION; double mostlyThere = (POLL_COUNT * 1) / 2; int slowerSpeed = FREQUENCY / 4; -- 2.47.2 From 20a4b8a81ab15e94940029e96eb0775375e5462b Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Thu, 2 Mar 2023 15:51:42 -0500 Subject: [PATCH 03/12] End of Mar. 2 commit Finally got more information on how the motor for the fixture works. Started writing automation for motor control mechanisms. --- src/main/java/org/baxter/disco/ocr/Cli.java | 31 ++- .../org/baxter/disco/ocr/ConfigFacade.java | 54 +--- .../org/baxter/disco/ocr/MovementFacade.java | 252 ++++++++---------- 3 files changed, 146 insertions(+), 191 deletions(-) diff --git a/src/main/java/org/baxter/disco/ocr/Cli.java b/src/main/java/org/baxter/disco/ocr/Cli.java index 37badb7..b0e403e 100644 --- a/src/main/java/org/baxter/disco/ocr/Cli.java +++ b/src/main/java/org/baxter/disco/ocr/Cli.java @@ -58,7 +58,7 @@ public class Cli /** * Number of options currently available in the movement sub-menu. */ - private static final int movementMenuOptionCount = 3; + private static final int movementMenuOptionCount = 5; /** * Number of options currently available in the camera configuration sub-menu. @@ -285,10 +285,13 @@ public class Cli println("------------------------------------"); println("Current Frequency: " + MovementFacade.getUserFrequency() + "KHz"); println("Current Motor Time-out: " + MovementFacade.getTimeout()); + println("After " + (MovementFacade.getSlowFraction() * 100) + "% of the movement, motor speed will be " + MovementFacade.getSlowFactor() + "x slower."); println("------------------------------------"); println("1. Change Frequency"); println("2. Change Motor Time-out"); - println("3. Exit"); + println("3. Change Slow-down Point"); + println("4. Change Slow-down Amount"); + println("5. Exit"); println("===================================="); } @@ -421,7 +424,9 @@ public class Cli * Menu options: * 1. Change Frequency * 2. Change Motor Time-out - * 3. Exit + * 3. Change slow-down point + * 4. Change slow-down amount + * 5. Exit */ case 1: prompt("Input the desired frequency value (in KHz): "); @@ -440,12 +445,30 @@ public class Cli break; } case 3: + prompt("Input the desired percentage of travel to be slower: "); + double newSlowFraction = inputFiltering(inputScanner.nextLine()); + if(newSlowFraction != -1) + { + newSlowFraction = newSlowFraction / 100; + MovementFacade.setSlowFraction(newSlowFraction); + break; + } + case 4: + prompt("Input the desired speed reduction factor: "); + double newSpeedReduceFactor = inputFiltering(inputScanner.nextLine()); + if(newSpeedReduceFactor != -1) + { + MovementFacade.setSlowFactor(newSpeedReduceFactor); + break; + } + case movementMenuOptionCount: break; default: ErrorLogging.logError("User Input Error!!! - Invalid input."); } } - while(userInput != 3); + while(userInput != movementMenuOptionCount); + ConfigFacade.saveCurrentConfig(); } /** diff --git a/src/main/java/org/baxter/disco/ocr/ConfigFacade.java b/src/main/java/org/baxter/disco/ocr/ConfigFacade.java index 17337db..0ecb1f0 100644 --- a/src/main/java/org/baxter/disco/ocr/ConfigFacade.java +++ b/src/main/java/org/baxter/disco/ocr/ConfigFacade.java @@ -11,6 +11,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Iterator; //Apache Commons Configuration imports import org.apache.commons.configuration2.INIConfiguration; @@ -60,15 +61,6 @@ public class ConfigFacade */ private static final Map DUT_SERIALS = new HashMap<>(); - private static final String MOVEMENT_SECTION = "Fixture"; - private static int MOVEMENT_FREQ; - - private static final int MOVEMENT_DEFAULT_FREQ = 70000; - - private static double MOVEMENT_TIME_OUT; - - private static final double MOVEMENT_DEFAULT_TIME_OUT = 2.5; - /** * Builder for the main Configuration object. * @@ -152,30 +144,6 @@ public class ConfigFacade CONFIG_BUILDER.setAutoSave(true); } - /** - * - */ - public static double getFixtureValue(FixtureValues value) - { - switch (value) - { - case FREQUENCY: return (double)MOVEMENT_FREQ; - case TIMEOUT: return MOVEMENT_TIME_OUT; - default: return -1.0; - } - } - - public static boolean setFixtureValue(FixtureValues type, double value) - { - boolean output = true; - switch(type) - { - case FREQUENCY: MOVEMENT_FREQ = (int)value; - case TIMEOUT: MOVEMENT_TIME_OUT = value; - default: - } - return output; - } /** * Get a given config value. * All values are stored as doubles. @@ -364,9 +332,6 @@ public class ConfigFacade configMap.put(camera,cameraConfig); } - CONFIG_STORE.setProperty(MOVEMENT_SECTION + "." + "frequency", MOVEMENT_DEFAULT_FREQ); - CONFIG_STORE.setProperty(MOVEMENT_SECTION + "." + "timeout", MOVEMENT_DEFAULT_TIME_OUT); - //Save out to the file try { @@ -411,9 +376,6 @@ public class ConfigFacade } } - CONFIG_STORE.setProperty(MOVEMENT_SECTION + "." + "frequency", MOVEMENT_FREQ); - CONFIG_STORE.setProperty(MOVEMENT_SECTION + "." + "timeout", MOVEMENT_TIME_OUT); - //Save to the file try { @@ -471,17 +433,6 @@ public class ConfigFacade //Iterate over the imported object, saving the file's config values to the map Set configSections = CONFIG_STORE.getSections(); - if(configSections.remove(MOVEMENT_SECTION)) - { - SubnodeConfiguration fixtureConfiguration = CONFIG_STORE.getSection(MOVEMENT_SECTION); - MOVEMENT_FREQ = fixtureConfiguration.getInt("frequency"); - MOVEMENT_TIME_OUT = fixtureConfiguration.getDouble("timeout"); - } - else - { - MOVEMENT_FREQ = MOVEMENT_DEFAULT_FREQ; - MOVEMENT_TIME_OUT = MOVEMENT_DEFAULT_TIME_OUT; - } for(String sectionName : configSections) { Map savedSection = new HashMap<>(); @@ -534,7 +485,4 @@ public class ConfigFacade * @return true if loaded successfully, otherwise false */ public static boolean loadConfig() { return loadConfig(configFileLocation); } - - public enum FixtureValues - { FREQUENCY, TIMEOUT; } } diff --git a/src/main/java/org/baxter/disco/ocr/MovementFacade.java b/src/main/java/org/baxter/disco/ocr/MovementFacade.java index becdd8e..3b36d82 100644 --- a/src/main/java/org/baxter/disco/ocr/MovementFacade.java +++ b/src/main/java/org/baxter/disco/ocr/MovementFacade.java @@ -62,41 +62,68 @@ public class MovementFacade private static Thread runSwitchThread; /** - * Internal PWM Frequency + * Conversion factor from cm/s to PWM frequency. + * + * PWM to linear speed conversion: + * Motor controller should be set to 6400 pulses / revolution + * (See motor controller documentation) + * Fixture corkscrew has a lead of 3 revolutions / cm + * (Lead = thread pitch * # of thread starts) + * + * Frequency (Hz) = Speed (cm/s) * (pulses/rev) * (lead) */ - private static int FREQUENCY; + private static final int PWM_FREQ_CONVERT = 19200; /** - * Conversion factor for freq to FREQUENCY + * Max allowed frequency by current fixture design. */ - private static final int FREQUENCY_UNITS = 1000; + //private static int MAX_FREQUENCY = 175000; /** - * Max allowable frequency by current fixture design. + * Max allowed speed by current fixture design. + * Motor appears to start acting erratically over 192kHz. */ - private static final int MAX_FREQUENCY = 175000; + private static final double MAX_SPEED = 10.0; /** - * Minimum allowed frequency; also used for reset travels. + * Amount of buffer between the found absolute speed, and used speed. */ - private static final int MIN_FREQUENCY = 25000; - - //Externally Available Variables - /** - * Human-readable frequency - */ - private static double freq; + private static final double SPEED_BUFFER = 0.2; /** - * PWM Duty Cycle + * Minimum allowed speed of the fixture arm; also used for reset travels. + */ + private static final double MIN_SPEED = 0.5; + + /** + * minimum speed, represented as frequency + */ + private static final int MIN_FREQUENCY = (int)(MIN_SPEED * PWM_FREQ_CONVERT); + + /** + * Distance in cm the fixture needs to travel. + * + * Distance between limit switches: ~80cm. + * Thickness of fixture arm: ~30cm. + */ + private static final int TRAVEL_DIST = 30; + + private static final double STEP_1 = 2/3; + private static final double STEP_2 = 5/6; + + /** + * Speed of the fixture arm, in cm/s + */ + private static double SPEED = MIN_SPEED; + + private static int FREQUENCY = (int)(SPEED * PWM_FREQ_CONVERT); + + /** + * PWM Duty Cycle. + * Does not affect motor speed; necessary for PWM setup. */ private static final int DUTY_CYCLE = 50; - /** - * Number of seconds to wait before timing out a fixture movement. - */ - private static double TIME_OUT; - //PWM Addresses //All addresses are in BCM format. @@ -208,10 +235,6 @@ public class MovementFacade static { - FREQUENCY = (int)ConfigFacade.getFixtureValue(ConfigFacade.FixtureValues.FREQUENCY); - freq = FREQUENCY / FREQUENCY_UNITS; - TIME_OUT = (int)ConfigFacade.getFixtureValue(ConfigFacade.FixtureValues.TIMEOUT); - //ErrorLogging.logError("DEBUG: Starting lock thread..."); runSwitchThread = new Thread(() -> { @@ -253,7 +276,7 @@ public class MovementFacade //Initialise PWM object. This object is never used, //as the PWM signal is simply a clock for the motor. pwm = pwmBuilder("pwm","PWM Pin",PWM_PIN_ADDR); - pwm.on(DUTY_CYCLE, FREQUENCY); + //pwm.on(DUTY_CYCLE, FREQUENCY); } @@ -287,7 +310,6 @@ public class MovementFacade .address(address) .pwmType(PwmType.HARDWARE) .provider("pigpio-pwm") - .frequency(FREQUENCY) .initial(1) //On program close, turn off PWM. .shutdown(0); @@ -300,7 +322,6 @@ public class MovementFacade .address(address) .pwmType(PwmType.SOFTWARE) .provider("pigpio-pwm") - .frequency(FREQUENCY) .initial(1) //On program close, turn off PWM. .shutdown(0); @@ -352,84 +373,76 @@ public class MovementFacade } /** - * Getter for the fixture's PWM duty cycle. - * - * @return The current DutyCycle. + * Function used to locate the fixture's motor. */ - public static int getDutyCycle() { return DUTY_CYCLE; } - - /** - * Setter for the fixture's time to give up on a movement. - * - * @param newTimeout The new timeout (in seconds) to be set by the user. - * - * @return True if the value was set successfully; otherwise false. - */ - public static boolean setTimeout(double newTimeout) + private static void resetArm() { - boolean output = false; - if(newTimeout < 0) + pwm.on(DUTY_CYCLE, MIN_FREQUENCY); + if(!upperLimit.isHigh()) { - ErrorLogging.logError("Movement error!!! - Invalid timeout input."); + motorDirection.low(); + motorEnable.on(); + try{ Thread.sleep(50); } + catch (Exception e){ ErrorLogging.logError(e); } + motorEnable.off(); } - else - { - TIME_OUT = newTimeout; - ConfigFacade.setFixtureValue(ConfigFacade.FixtureValues.TIMEOUT, newTimeout); - output = true; - } - return output; + motorDirection.high(); + motorEnable.on(); + while(!upperLimit.isHigh()) {} + motorEnable.off(); } /** - * Getter for the fixture's time to give up on a movement. - * - * @return The current timeout. + * Used to set the motor's max speed. */ - public static double getTimeout() { return TIME_OUT; } - - /** - * Setter for the fixture's PWM frequency. - * - * @param newFrequency The new frequency to be set by the user. - * - * @return True if the value was set successfully; otherwise false. - */ - public static boolean setFrequency(double newFrequency) + public static void calibrate() { - boolean output = false; - if(newFrequency < 0) - { - ErrorLogging.logError("Movement error! - Invalid frequency input."); - } - else if(newFrequency > MAX_FREQUENCY) - { - ErrorLogging.logError("Movement warning!!! - Value above maximum allowed."); - } - else - { - freq = newFrequency; - ConfigFacade.setFixtureValue(ConfigFacade.FixtureValues.FREQUENCY, newFrequency); - FREQUENCY = (int)(freq * FREQUENCY_UNITS); - pwm.on(DUTY_CYCLE, FREQUENCY); - output = true; - } - return output; + resetArm(); + SPEED = calib(1, MAX_SPEED, 1); + SPEED = calib(SPEED,(SPEED+1),0.1); + setSpeed(SPEED - SPEED_BUFFER); } /** - * Getter for the fixture's PWM frequency, in hertz. + * Find the max speed of the fixure between two points. * - * @return The current PWM frequency. + * @param start Lowest speed to check + * @param max Highest speed to check + * @param iterate How much to iterate by + * + * @return The largest safe value between start and max. */ - public static int getFrequency() { return FREQUENCY; } + private static double calib(double start, double max, double iterate) + { + for(double i = start; i < max; i+=iterate) + { + setSpeed(i); + motorDirection.low(); + motorEnable.on(); + try{ Thread.sleep(100); } + catch (Exception e){ ErrorLogging.logError(e); } + motorEnable.off(); + if(upperLimit.isHigh()) + return i; + else + resetArm(); + } + return max; + } /** - * Getter for the fixture's PWM frequency, in KHz. + * Safely set the speed of the motor and fixture. * - * @return The current PWM frequency. + * @return true if set successfully, else false */ - public static double getUserFrequency() { return freq; } + private static boolean setSpeed(double newSpeed) + { + if(newSpeed < MIN_SPEED || newSpeed > MAX_SPEED) return false; + SPEED = newSpeed; + FREQUENCY = (int)(SPEED * PWM_FREQ_CONVERT); + pwm.on(DUTY_CYCLE, FREQUENCY); + return true; + } /** * Internal function to send the fixture to a given limit switch. @@ -442,7 +455,7 @@ public class MovementFacade * @param timeout How long (in seconds) to wait before timing out. * @return true if movement was successful; otherwise false */ - private static FinalState gotoLimit(boolean moveUp, double timeout) + private static FinalState gotoLimit(boolean moveUp) { FinalState output = FinalState.FAILED; DigitalInput limitSense; @@ -450,37 +463,33 @@ public class MovementFacade { motorDirection.high(); limitSense = upperLimit; - ErrorLogging.logError("DEBUG: Sending fixture up..."); + ErrorLogging.logError("Sending fixture up..."); } else { motorDirection.low(); limitSense = lowerLimit; - ErrorLogging.logError("DEBUG: Sending fixture down..."); + ErrorLogging.logError("Sending fixture down..."); } if(limitSense.isHigh()) return FinalState.SAFE; - double POLL_COUNT = timeout * TIME_CONVERSION; - double mostlyThere = (POLL_COUNT * 1) / 2; - int slowerSpeed = FREQUENCY / 4; + FREQUENCY = (int)(SPEED * TIME_CONVERSION); + int TRAVEL_TIME = (int)(TRAVEL_DIST / SPEED); + int POLL_COUNT = TRAVEL_TIME * TIME_CONVERSION; + int VEL_STEP_1 = (int)(STEP_1 * POLL_COUNT); + int VEL_STEP_2 = (int)(STEP_2 * POLL_COUNT); + //int mostlyThere = (int) (POLL_COUNT * slowFraction); + //int slowerSpeed = (int) (FREQUENCY / speedReduceFactor); motorEnable.on(); for(int i = 0; i < (POLL_COUNT);i++) { - try{ Thread.sleep(POLL_WAIT); } - catch(Exception e) {ErrorLogging.logError(e);}; - - if(limitSense.isHigh()) - { - output = ( (i >= mostlyThere) ? FinalState.SAFE : FinalState.UNSAFE); - break; - } - else if(i >= mostlyThere) - { - pwm.on(DUTY_CYCLE, slowerSpeed); - continue; - } + try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); } + if(i >= VEL_STEP_1 && i < VEL_STEP_2) + pwm.on(DUTY_CYCLE, FREQUENCY / 2); + else if(i >= VEL_STEP_2) + pwm.on(DUTY_CYCLE, FREQUENCY / 4); } if(output == FinalState.FAILED) @@ -490,44 +499,19 @@ public class MovementFacade return output; } - public static void reset() - { - pwm.on(DUTY_CYCLE, MIN_FREQUENCY); - goUp(Double.POSITIVE_INFINITY); - pwm.on(DUTY_CYCLE, FREQUENCY); - } - /** * Send the fixture to the lower limit switch. * - * @param timeout How long (in seconds) to wait before timing out. * @return true if movement was successful; otherwise false */ - public static FinalState goDown(double timeout) { return gotoLimit(false, timeout); } + public static FinalState goDown() { return gotoLimit(false); } /** * Send the fixture to the upper limit switch. * - * @param timeout How long (in seconds) to wait before timing out. * @return true if movement was successful; otherwise false */ - public static FinalState goUp(double timeout) { return gotoLimit(true, timeout); } - - /** - * Send the fixture to the lower limit switch. - * Timeout defaults to {@link #TIME_OUT}. - * - * @return true if movement was successful; otherwise false - */ - public static FinalState goDown() { return goDown(TIME_OUT); } - - /** - * Send the fixture to the upper limit switch. - * Timeout defaults to {@link #TIME_OUT}. - * - * @return true if movement was successful; otherwise false - */ - public static FinalState goUp() { return goUp(TIME_OUT); } + public static FinalState goUp() { return gotoLimit(true); } /** * Extends the piston for 1 second, pushing the button on the DUT. @@ -546,7 +530,7 @@ public class MovementFacade */ public static void closeGPIO() { - reset(); + resetArm(); if(runSwitchThread.isAlive()) { exit = true; -- 2.47.2 From fb79023fd49b73432ea120c1fe8a50295d96dea5 Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Fri, 3 Mar 2023 15:54:40 -0500 Subject: [PATCH 04/12] End of day Mar. 3 commit Motor calibration set up properly. Started removing extra option from main menu. `gotoLimit` potentially broken due to bad values in `VEL_STEP_1` and `VEL_STEP_2`. Need to look further into these values. ConfigFacade currently broken, due to previously-attempted saving of fixture config values to config file. Needs to be rectified. --- src/main/java/org/baxter/disco/ocr/Cli.java | 239 +++++++++--------- .../org/baxter/disco/ocr/MovementFacade.java | 99 +++++++- 2 files changed, 198 insertions(+), 140 deletions(-) diff --git a/src/main/java/org/baxter/disco/ocr/Cli.java b/src/main/java/org/baxter/disco/ocr/Cli.java index b0e403e..a816065 100644 --- a/src/main/java/org/baxter/disco/ocr/Cli.java +++ b/src/main/java/org/baxter/disco/ocr/Cli.java @@ -53,7 +53,7 @@ public class Cli /** * Number of options currently available in the main menu. */ - private static final int mainMenuOptionCount = 8; + private static final int mainMenuOptionCount = 7; /** * Number of options currently available in the movement sub-menu. @@ -92,7 +92,7 @@ public class Cli int userInput = 0; - boolean fixtureTested = false; + MovementFacade.calibrate(); do { @@ -102,33 +102,21 @@ public class Cli switch (userInput) { case 1: - testMovement(); - fixtureTested = true; - break; - case 2: println("Setting up cameras..."); println("This may take a moment..."); configureCameras(); camerasConfigured = true; break; - case 3: + case 2: setDUTSerials(); break; - case 4: + case 3: setIterationCount(); break; - case 5: + case 4: setActiveCameras(); break; - case 6: - if(!fixtureTested) - { - prompt("You have not tested the fixture's movement configuration! Are you sure you would like to continue? (y/N): "); - String input = inputScanner.nextLine().toLowerCase().trim(); - if(input.isBlank() || input.charAt(0) != 'y') break; - else - ErrorLogging.logError("DEBUG: Potential for error: Untested fixture movement config."); - } + case 5: if(!camerasConfigured) { prompt("You have not configured the cameras yet! Are you sure you would like to continue? (y/N): "); @@ -156,7 +144,7 @@ public class Cli runTests(); break; - case 7: + case 6: printHelp(); break; case 8: @@ -263,37 +251,36 @@ public class Cli println("--------------------------------------"); println("Current iteration count: " + iterationCount); println("--------------------------------------"); - println("1. Test and configure fixture movement"); - println("2. Configure camera"); - println("3. Set serial numbers"); - println("4. Change test iteration count"); - println("5. Toggle active cameras"); - println("6. Run tests"); - println("7. Help"); - println("8. Exit"); + println("1. Configure camera"); + println("2. Set serial numbers"); + println("3. Change test iteration count"); + println("4. Toggle active cameras"); + println("5. Run tests"); + println("6. Help"); + println("7. Exit"); println("======================================"); } /** * Predefined print statements for the movement submenu. */ - private static void printMovementMenu() - { - println("\n\n"); - println("===================================="); - println("Movement Menu:"); - println("------------------------------------"); - println("Current Frequency: " + MovementFacade.getUserFrequency() + "KHz"); - println("Current Motor Time-out: " + MovementFacade.getTimeout()); - println("After " + (MovementFacade.getSlowFraction() * 100) + "% of the movement, motor speed will be " + MovementFacade.getSlowFactor() + "x slower."); - println("------------------------------------"); - println("1. Change Frequency"); - println("2. Change Motor Time-out"); - println("3. Change Slow-down Point"); - println("4. Change Slow-down Amount"); - println("5. Exit"); - println("===================================="); - } + //private static void printMovementMenu() + //{ + // println("\n\n"); + // println("===================================="); + // println("Movement Menu:"); + // println("------------------------------------"); + // println("Current Frequency: " + MovementFacade.getUserFrequency() + "KHz"); + // println("Current Motor Time-out: " + MovementFacade.getTimeout()); + // println("After " + (MovementFacade.getSlowFraction() * 100) + "% of the movement, motor speed will be " + MovementFacade.getSlowFactor() + "x slower."); + // println("------------------------------------"); + // println("1. Change Frequency"); + // println("2. Change Motor Time-out"); + // println("3. Change Slow-down Point"); + // println("4. Change Slow-down Amount"); + // println("5. Exit"); + // println("===================================="); + //} /** * Pre-defined method for printing all available cameras in a menu @@ -386,89 +373,89 @@ public class Cli */ private static void testMovement() { - int userInput = -1; //Loop to allow multiple changes to device GPIO settings - do - { - println("Testing movement..."); - MovementFacade.reset(); - MovementFacade.FinalState downwardMove = MovementFacade.goDown(); - switch(downwardMove) - { - case UNSAFE: - ErrorLogging.logError("Movement warning!!! - Time-out too long, motor does not slow down before hitting limit switch. Consider setting this value to be lower."); - break; - case FAILED: - ErrorLogging.logError("Movement warning!!! - Fixture did not hit lower limit switch. Consider changing frequency."); - ErrorLogging.logError("Fixture movement configuration: Downward movement missed."); - break; - case SAFE: - MovementFacade.FinalState returnMove = MovementFacade.goUp(); - switch(returnMove) - { - case UNSAFE: - ErrorLogging.logError("Movement warning!!! - Time-out too long, motor does not slow down before hitting limit switch. Consider setting this value to be lower."); - break; - case FAILED: - ErrorLogging.logError("Movement warning!!! - Fixture did not hit upper limit switch. Consider changing frequency."); - ErrorLogging.logError("Fixture movement configuration: Return movement missed."); - break; - case SAFE: - } - } - printMovementMenu(); - userInput = (int)inputFiltering(inputScanner.nextLine()); - switch (userInput) - { - /* - * Menu options: - * 1. Change Frequency - * 2. Change Motor Time-out - * 3. Change slow-down point - * 4. Change slow-down amount - * 5. Exit - */ - case 1: - prompt("Input the desired frequency value (in KHz): "); - int newFrequency = (int)inputFiltering(inputScanner.nextLine()); - if (newFrequency != -1) - { - MovementFacade.setFrequency(newFrequency); - break; - } - case 2: - prompt("Input the desired time-out (in seconds): "); - double newTimeout = inputFiltering(inputScanner.nextLine()); - if (newTimeout != -1) - { - MovementFacade.setTimeout(newTimeout); - break; - } - case 3: - prompt("Input the desired percentage of travel to be slower: "); - double newSlowFraction = inputFiltering(inputScanner.nextLine()); - if(newSlowFraction != -1) - { - newSlowFraction = newSlowFraction / 100; - MovementFacade.setSlowFraction(newSlowFraction); - break; - } - case 4: - prompt("Input the desired speed reduction factor: "); - double newSpeedReduceFactor = inputFiltering(inputScanner.nextLine()); - if(newSpeedReduceFactor != -1) - { - MovementFacade.setSlowFactor(newSpeedReduceFactor); - break; - } - case movementMenuOptionCount: - break; - default: - ErrorLogging.logError("User Input Error!!! - Invalid input."); - } - } - while(userInput != movementMenuOptionCount); - ConfigFacade.saveCurrentConfig(); + MovementFacade.calibrate(); + //do + //{ + // println("Testing movement..."); + // //MovementFacade.reset(); + // MovementFacade.FinalState downwardMove = MovementFacade.goDown(); + // switch(downwardMove) + // { + // case UNSAFE: + // ErrorLogging.logError("Movement warning!!! - Time-out too long, motor does not slow down before hitting limit switch. Consider setting this value to be lower."); + // break; + // case FAILED: + // ErrorLogging.logError("Movement warning!!! - Fixture did not hit lower limit switch. Consider changing frequency."); + // ErrorLogging.logError("Fixture movement configuration: Downward movement missed."); + // break; + // case SAFE: + // MovementFacade.FinalState returnMove = MovementFacade.goUp(); + // switch(returnMove) + // { + // case UNSAFE: + // ErrorLogging.logError("Movement warning!!! - Time-out too long, motor does not slow down before hitting limit switch. Consider setting this value to be lower."); + // break; + // case FAILED: + // ErrorLogging.logError("Movement warning!!! - Fixture did not hit upper limit switch. Consider changing frequency."); + // ErrorLogging.logError("Fixture movement configuration: Return movement missed."); + // break; + // case SAFE: + // } + // } + // //printMovementMenu(); + // userInput = (int)inputFiltering(inputScanner.nextLine()); + // switch (userInput) + // { + // /* + // * Menu options: + // * 1. Change Frequency + // * 2. Change Motor Time-out + // * 3. Change slow-down point + // * 4. Change slow-down amount + // * 5. Exit + // */ + // case 1: + // prompt("Input the desired frequency value (in KHz): "); + // int newFrequency = (int)inputFiltering(inputScanner.nextLine()); + // if (newFrequency != -1) + // { + // //MovementFacade.setFrequency(newFrequency); + // break; + // } + // case 2: + // prompt("Input the desired time-out (in seconds): "); + // double newTimeout = inputFiltering(inputScanner.nextLine()); + // if (newTimeout != -1) + // { + // //MovementFacade.setTimeout(newTimeout); + // break; + // } + // case 3: + // prompt("Input the desired percentage of travel to be slower: "); + // double newSlowFraction = inputFiltering(inputScanner.nextLine()); + // if(newSlowFraction != -1) + // { + // newSlowFraction = newSlowFraction / 100; + // MovementFacade.setSlowFraction(newSlowFraction); + // break; + // } + // case 4: + // prompt("Input the desired speed reduction factor: "); + // double newSpeedReduceFactor = inputFiltering(inputScanner.nextLine()); + // if(newSpeedReduceFactor != -1) + // { + // MovementFacade.setSlowFactor(newSpeedReduceFactor); + // break; + // } + // case movementMenuOptionCount: + // break; + // default: + // ErrorLogging.logError("User Input Error!!! - Invalid input."); + // } + //} + //while(userInput != movementMenuOptionCount); + //ConfigFacade.saveCurrentConfig(); } /** diff --git a/src/main/java/org/baxter/disco/ocr/MovementFacade.java b/src/main/java/org/baxter/disco/ocr/MovementFacade.java index 3b36d82..b6f4506 100644 --- a/src/main/java/org/baxter/disco/ocr/MovementFacade.java +++ b/src/main/java/org/baxter/disco/ocr/MovementFacade.java @@ -104,7 +104,7 @@ public class MovementFacade * Distance in cm the fixture needs to travel. * * Distance between limit switches: ~80cm. - * Thickness of fixture arm: ~30cm. + * Thickness of fixture arm: ~50cm. */ private static final int TRAVEL_DIST = 30; @@ -375,21 +375,39 @@ public class MovementFacade /** * Function used to locate the fixture's motor. */ - private static void resetArm() + public static int resetArm() { + int counter = 0; + //ErrorLogging.logError("Setting minimum frequency of PWM..."); + //ErrorLogging.logError("Minimum frequency: " + MIN_FREQUENCY); pwm.on(DUTY_CYCLE, MIN_FREQUENCY); - if(!upperLimit.isHigh()) + if(upperLimit.isHigh()) { + //ErrorLogging.logError("Motor at highest point! Lowering to reset."); motorDirection.low(); + //ErrorLogging.logError("Motor on!"); motorEnable.on(); - try{ Thread.sleep(50); } + try{ Thread.sleep(500); } catch (Exception e){ ErrorLogging.logError(e); } motorEnable.off(); + //ErrorLogging.logError("Motor off!"); } + //ErrorLogging.logError("Moving motor to highest point."); motorDirection.high(); + + //ErrorLogging.logError("Motor on!"); motorEnable.on(); - while(!upperLimit.isHigh()) {} + + //ErrorLogging.logError("Is the upper limit switch reached? " + upperLimit.isHigh()); + while(!upperLimit.isHigh()) + { + try{ Thread.sleep(100); } + catch (Exception e) { ErrorLogging.logError(e); } + counter++; + } motorEnable.off(); + //ErrorLogging.logError("Motor returned after " + counter + " polls."); + return counter; } /** @@ -397,9 +415,13 @@ public class MovementFacade */ public static void calibrate() { + ErrorLogging.logError("Initial Calibration reset."); resetArm(); + ErrorLogging.logError("Coarse calibrating..."); SPEED = calib(1, MAX_SPEED, 1); + ErrorLogging.logError("Coarse calibrating..."); SPEED = calib(SPEED,(SPEED+1),0.1); + ErrorLogging.logError("Speed set to " + (SPEED - SPEED_BUFFER)); setSpeed(SPEED - SPEED_BUFFER); } @@ -414,20 +436,44 @@ public class MovementFacade */ private static double calib(double start, double max, double iterate) { + ErrorLogging.logError("Calibrating. Iterating from " + start + " to " + max + " in " + iterate + " steps."); for(double i = start; i < max; i+=iterate) { - setSpeed(i); + //ErrorLogging.logError("Testing speed " + i + "..."); + if(!setSpeed(i)) + { + //ErrorLogging.logError("Speed set unsuccessfully! returning " + MIN_SPEED + "..."); + return MIN_SPEED; + } + //ErrorLogging.logError("Motor travelling down."); motorDirection.low(); + //ErrorLogging.logError("Motor on!"); motorEnable.on(); - try{ Thread.sleep(100); } - catch (Exception e){ ErrorLogging.logError(e); } + for(int j = 0; j < 5; j++) + { + try{ Thread.sleep(100); } + catch (Exception e){ ErrorLogging.logError(e); } + if(lowerLimit.isHigh()) break; + } motorEnable.off(); + //ErrorLogging.logError("Motor off!"); if(upperLimit.isHigh()) - return i; + { + ErrorLogging.logError("Motor faild to move! Returning " + (i - iterate)); + return i-iterate; + } else - resetArm(); + { + + //ErrorLogging.logError("Motor moved at speed " + i + ". Checking for errors."); + if(resetArm() < 10 && i > 1.0) + { + ErrorLogging.logError("Motor failed to move! Returning " + (i - iterate)); + return i - iterate; + } + } } - return max; + return max-iterate; } /** @@ -437,11 +483,21 @@ public class MovementFacade */ private static boolean setSpeed(double newSpeed) { - if(newSpeed < MIN_SPEED || newSpeed > MAX_SPEED) return false; + boolean output; + if(newSpeed < MIN_SPEED || newSpeed > MAX_SPEED) + { + SPEED = MIN_SPEED; + output = false; + } + else + { + SPEED = newSpeed; + output = true; + } SPEED = newSpeed; FREQUENCY = (int)(SPEED * PWM_FREQ_CONVERT); pwm.on(DUTY_CYCLE, FREQUENCY); - return true; + return output; } /** @@ -475,6 +531,7 @@ public class MovementFacade if(limitSense.isHigh()) return FinalState.SAFE; FREQUENCY = (int)(SPEED * TIME_CONVERSION); + pwm.on(DUTY_CYCLE, FREQUENCY); int TRAVEL_TIME = (int)(TRAVEL_DIST / SPEED); int POLL_COUNT = TRAVEL_TIME * TIME_CONVERSION; int VEL_STEP_1 = (int)(STEP_1 * POLL_COUNT); @@ -482,19 +539,33 @@ public class MovementFacade //int mostlyThere = (int) (POLL_COUNT * slowFraction); //int slowerSpeed = (int) (FREQUENCY / speedReduceFactor); + ErrorLogging.logError("Total Poll count: " + POLL_COUNT); + ErrorLogging.logError("Transition 1: " + VEL_STEP_1); + ErrorLogging.logError("Transition 2: " + VEL_STEP_2); + ErrorLogging.logError("Travel time: " + TRAVEL_TIME); + ErrorLogging.logError("Travel speed: " + SPEED); motorEnable.on(); for(int i = 0; i < (POLL_COUNT);i++) { + ErrorLogging.logError("Iteration " + i); try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); } if(i >= VEL_STEP_1 && i < VEL_STEP_2) + { + output = FinalState.UNSAFE; + ErrorLogging.logError("Slow down!"); pwm.on(DUTY_CYCLE, FREQUENCY / 2); + } else if(i >= VEL_STEP_2) + { + ErrorLogging.logError("Slow down more!"); pwm.on(DUTY_CYCLE, FREQUENCY / 4); + output = FinalState.SAFE; + } } + motorEnable.off(); if(output == FinalState.FAILED) ErrorLogging.logError("FIXTURE MOVEMENT ERROR! - Motor movement timed out!"); - motorEnable.off(); pwm.on(DUTY_CYCLE, FREQUENCY); return output; } -- 2.47.2 From cd0be06a9ffd27cb72ad8e14c1dd5a9cf58dcbeb Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Mon, 6 Mar 2023 15:08:57 -0500 Subject: [PATCH 05/12] Remove hardcoded camera locations also: - Temporarily comment out CLI menu to make movement testing faster - remove movement from config --- dependency-reduced-pom.xml | 2 +- pom.xml | 2 +- runScript.sh | 2 +- src/main/java/org/baxter/disco/ocr/Cli.java | 255 +++++------------- .../org/baxter/disco/ocr/ConfigFacade.java | 49 ++-- .../org/baxter/disco/ocr/MovementFacade.java | 142 +++++----- .../org/baxter/disco/ocr/OpenCVFacade.java | 27 +- 7 files changed, 179 insertions(+), 300 deletions(-) diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index bb421ef..546df28 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -4,7 +4,7 @@ org.baxter.disco ocr Disco OCR Accuracy Over Life Testing - 4.3.5 + 4.3.6 Testing Discos for long-term accuracy, using automated optical character recognition. Baxter International diff --git a/pom.xml b/pom.xml index af94a17..dee8341 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.baxter.disco ocr - 4.3.5 + 4.3.6 jar Disco OCR Accuracy Over Life Testing Testing Discos for long-term accuracy, using automated optical character recognition. diff --git a/runScript.sh b/runScript.sh index b4ef9f6..9cb3eae 100644 --- a/runScript.sh +++ b/runScript.sh @@ -1,2 +1,2 @@ #! /usr/bin/env sh -sudo java -jar discoTesting-4.3.5.jar 2>/dev/null +sudo java -jar discoTesting-4.3.6.jar 2>/dev/null diff --git a/src/main/java/org/baxter/disco/ocr/Cli.java b/src/main/java/org/baxter/disco/ocr/Cli.java index a816065..370d151 100644 --- a/src/main/java/org/baxter/disco/ocr/Cli.java +++ b/src/main/java/org/baxter/disco/ocr/Cli.java @@ -18,14 +18,14 @@ import java.util.concurrent.locks.ReentrantLock; * classes). * * @author Blizzard Finnegan - * @version 1.6.1, 10 Feb. 2023 + * @version 1.7.0, 06 Mar. 2023 */ public class Cli { /** * Complete build version number */ - private static final String version = "4.3.5"; + private static final String version = "4.3.6"; /** * Currently saved iteration count. @@ -55,11 +55,6 @@ public class Cli */ private static final int mainMenuOptionCount = 7; - /** - * Number of options currently available in the movement sub-menu. - */ - private static final int movementMenuOptionCount = 5; - /** * Number of options currently available in the camera configuration sub-menu. */ @@ -90,70 +85,71 @@ public class Cli ConfigFacade.init(); - int userInput = 0; + //int userInput = 0; + ErrorLogging.logError("Initialising fixture movement. This will take a few moments, and may produce some temporary unnerving sounds from the motor. This is expected behaviour, and no damage is being done."); MovementFacade.calibrate(); - do - { - printMainMenu(); - userInput = (int)inputFiltering(inputScanner.nextLine()); + //do + //{ + // printMainMenu(); + // userInput = (int)inputFiltering(inputScanner.nextLine()); - switch (userInput) - { - case 1: - println("Setting up cameras..."); - println("This may take a moment..."); - configureCameras(); - camerasConfigured = true; - break; - case 2: - setDUTSerials(); - break; - case 3: - setIterationCount(); - break; - case 4: - setActiveCameras(); - break; - case 5: - if(!camerasConfigured) - { - prompt("You have not configured the cameras yet! Are you sure you would like to continue? (y/N): "); - String input = inputScanner.nextLine().toLowerCase().trim(); - if( input.isBlank() || input.charAt(0) != 'y' ) break; - else - ErrorLogging.logError("DEBUG: Potential for error: Un-initialised cameras."); - } + // switch (userInput) + // { + // case 1: + // println("Setting up cameras..."); + // println("This may take a moment..."); + // configureCameras(); + // camerasConfigured = true; + // break; + // case 2: + // setDUTSerials(); + // break; + // case 3: + // setIterationCount(); + // break; + // case 4: + // setActiveCameras(); + // break; + // case 5: + // if(!camerasConfigured) + // { + // prompt("You have not configured the cameras yet! Are you sure you would like to continue? (y/N): "); + // String input = inputScanner.nextLine().toLowerCase().trim(); + // if( input.isBlank() || input.charAt(0) != 'y' ) break; + // else + // ErrorLogging.logError("DEBUG: Potential for error: Un-initialised cameras."); + // } - serialsSet = true; - for(String cameraName : OpenCVFacade.getCameraNames()) - { - if(ConfigFacade.getValue(cameraName,ConfigProperties.ACTIVE) != 0 && - ConfigFacade.getSerial(cameraName) == null ) - serialsSet = false; - } - if(!serialsSet) - { - prompt("You have not set the serial numbers for your DUTs yet! Are you sure you would like to continue? (y/N): "); - String input = inputScanner.nextLine().toLowerCase().trim(); - if( input.isBlank() || input.charAt(0) != 'y' ) break; - else - ErrorLogging.logError("DEBUG: Potential for error: Un-initialised DUT Serial numbers."); - } + // serialsSet = true; + // for(String cameraName : OpenCVFacade.getCameraNames()) + // { + // if(ConfigFacade.getValue(cameraName,ConfigProperties.ACTIVE) != 0 && + // ConfigFacade.getSerial(cameraName) == null ) + // serialsSet = false; + // } + // if(!serialsSet) + // { + // prompt("You have not set the serial numbers for your DUTs yet! Are you sure you would like to continue? (y/N): "); + // String input = inputScanner.nextLine().toLowerCase().trim(); + // if( input.isBlank() || input.charAt(0) != 'y' ) break; + // else + // ErrorLogging.logError("DEBUG: Potential for error: Un-initialised DUT Serial numbers."); + // } - runTests(); - break; - case 6: - printHelp(); - break; - case 8: - break; - default: - //Input handling already done by inputFiltering() - } + // runTests(); + // break; + // case 6: + // printHelp(); + // break; + // case 8: + // break; + // default: + // //Input handling already done by inputFiltering() + // } - } while (userInput != mainMenuOptionCount); + //} while (userInput != mainMenuOptionCount); } //If anything ever goes wrong, catch the error and exit @@ -261,27 +257,6 @@ public class Cli println("======================================"); } - /** - * Predefined print statements for the movement submenu. - */ - //private static void printMovementMenu() - //{ - // println("\n\n"); - // println("===================================="); - // println("Movement Menu:"); - // println("------------------------------------"); - // println("Current Frequency: " + MovementFacade.getUserFrequency() + "KHz"); - // println("Current Motor Time-out: " + MovementFacade.getTimeout()); - // println("After " + (MovementFacade.getSlowFraction() * 100) + "% of the movement, motor speed will be " + MovementFacade.getSlowFactor() + "x slower."); - // println("------------------------------------"); - // println("1. Change Frequency"); - // println("2. Change Motor Time-out"); - // println("3. Change Slow-down Point"); - // println("4. Change Slow-down Amount"); - // println("5. Exit"); - // println("===================================="); - //} - /** * Pre-defined method for printing all available cameras in a menu */ @@ -367,97 +342,6 @@ public class Cli println("===================================="); } - - /** - * Function for testing movement, and modifying hardware values - */ - private static void testMovement() - { - //Loop to allow multiple changes to device GPIO settings - MovementFacade.calibrate(); - //do - //{ - // println("Testing movement..."); - // //MovementFacade.reset(); - // MovementFacade.FinalState downwardMove = MovementFacade.goDown(); - // switch(downwardMove) - // { - // case UNSAFE: - // ErrorLogging.logError("Movement warning!!! - Time-out too long, motor does not slow down before hitting limit switch. Consider setting this value to be lower."); - // break; - // case FAILED: - // ErrorLogging.logError("Movement warning!!! - Fixture did not hit lower limit switch. Consider changing frequency."); - // ErrorLogging.logError("Fixture movement configuration: Downward movement missed."); - // break; - // case SAFE: - // MovementFacade.FinalState returnMove = MovementFacade.goUp(); - // switch(returnMove) - // { - // case UNSAFE: - // ErrorLogging.logError("Movement warning!!! - Time-out too long, motor does not slow down before hitting limit switch. Consider setting this value to be lower."); - // break; - // case FAILED: - // ErrorLogging.logError("Movement warning!!! - Fixture did not hit upper limit switch. Consider changing frequency."); - // ErrorLogging.logError("Fixture movement configuration: Return movement missed."); - // break; - // case SAFE: - // } - // } - // //printMovementMenu(); - // userInput = (int)inputFiltering(inputScanner.nextLine()); - // switch (userInput) - // { - // /* - // * Menu options: - // * 1. Change Frequency - // * 2. Change Motor Time-out - // * 3. Change slow-down point - // * 4. Change slow-down amount - // * 5. Exit - // */ - // case 1: - // prompt("Input the desired frequency value (in KHz): "); - // int newFrequency = (int)inputFiltering(inputScanner.nextLine()); - // if (newFrequency != -1) - // { - // //MovementFacade.setFrequency(newFrequency); - // break; - // } - // case 2: - // prompt("Input the desired time-out (in seconds): "); - // double newTimeout = inputFiltering(inputScanner.nextLine()); - // if (newTimeout != -1) - // { - // //MovementFacade.setTimeout(newTimeout); - // break; - // } - // case 3: - // prompt("Input the desired percentage of travel to be slower: "); - // double newSlowFraction = inputFiltering(inputScanner.nextLine()); - // if(newSlowFraction != -1) - // { - // newSlowFraction = newSlowFraction / 100; - // MovementFacade.setSlowFraction(newSlowFraction); - // break; - // } - // case 4: - // prompt("Input the desired speed reduction factor: "); - // double newSpeedReduceFactor = inputFiltering(inputScanner.nextLine()); - // if(newSpeedReduceFactor != -1) - // { - // MovementFacade.setSlowFactor(newSpeedReduceFactor); - // break; - // } - // case movementMenuOptionCount: - // break; - // default: - // ErrorLogging.logError("User Input Error!!! - Invalid input."); - // } - //} - //while(userInput != movementMenuOptionCount); - //ConfigFacade.saveCurrentConfig(); - } - /** * Sub-function used to configure cameras. */ @@ -802,7 +686,9 @@ public class Cli */ private static void close() { + ErrorLogging.logError("DEBUG: ================="); ErrorLogging.logError("DEBUG: PROGRAM CLOSING."); + ErrorLogging.logError("DEBUG: ================="); if(inputScanner != null) inputScanner.close(); MovementFacade.closeGPIO(); ErrorLogging.logError("DEBUG: END OF PROGRAM."); @@ -852,13 +738,6 @@ public class Cli output = -1; } break; - case MOVEMENT: - if(output > movementMenuOptionCount) - { - invalidMovementMenuInput(); - output = -1; - } - break; case CAMERA: if(output > cameraMenuOptionCount) { @@ -881,14 +760,6 @@ public class Cli invalidMenuInput(mainMenuOptionCount); } - /** - * Prints a message when user inputs an invalid main menu value. - */ - private static void invalidMovementMenuInput() - { - invalidMenuInput(movementMenuOptionCount); - } - /** * Prints a message when user inputs an invalid main menu value. */ @@ -939,5 +810,5 @@ public class Cli /** * Enum of possible menus available */ - private enum Menus { MAIN,MOVEMENT,CAMERA,OTHER; } + private enum Menus { MAIN,CAMERA,OTHER; } } diff --git a/src/main/java/org/baxter/disco/ocr/ConfigFacade.java b/src/main/java/org/baxter/disco/ocr/ConfigFacade.java index 0ecb1f0..48ba6ef 100644 --- a/src/main/java/org/baxter/disco/ocr/ConfigFacade.java +++ b/src/main/java/org/baxter/disco/ocr/ConfigFacade.java @@ -11,11 +11,9 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.time.LocalDateTime; import java.util.ArrayList; -import java.util.Iterator; //Apache Commons Configuration imports import org.apache.commons.configuration2.INIConfiguration; -import org.apache.commons.configuration2.SubnodeConfiguration; import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder; import org.apache.commons.configuration2.builder.fluent.Parameters; @@ -280,6 +278,7 @@ public class ConfigFacade //********************************************** //SAVE AND LOAD SETTINGS //********************************************** + // /** * Save current config to a user-defined file location. @@ -436,22 +435,10 @@ public class ConfigFacade for(String sectionName : configSections) { Map savedSection = new HashMap<>(); - String subSectionPrefix = ""; for(String cameraName : cameraNames) { - if(sectionName.equals(cameraName)) - { - subSectionPrefix = cameraName; - break; - } - } - - //If an imported section fails, fallback to saving the default values to - //the given location - if(subSectionPrefix.equals("")) - { - ErrorLogging.logError("CONFIG LOAD ERROR!!! - Failed import from file. Setting default config."); - return saveDefaultConfig(filename); + if(!sectionName.equals(cameraName)) + { saveSingleDefault(cameraName); } } for(ConfigProperties configState : ConfigProperties.values()) @@ -485,4 +472,34 @@ public class ConfigFacade * @return true if loaded successfully, otherwise false */ public static boolean loadConfig() { return loadConfig(configFileLocation); } + + /** + * Save default values to a single camera's config. + * + * @param sectionName Name of the config section being saved to. + * + * @return false if error, else true + */ + private static boolean saveSingleDefault(String sectionName) + { + boolean output = false; + Map cameraConfig = new HashMap<>(); + for(ConfigProperties property : ConfigProperties.values()) + { + String propertyName = sectionName + "." + property.getConfig(); + double propertyValue = property.getDefaultValue(); + cameraConfig.put(property,propertyValue); + //ErrorLogging.logError("DEBUG: Attempting to save to config: "); + //ErrorLogging.logError("DEBUG: " + propertyName + ", " + propertyValue); + CONFIG_STORE.setProperty(propertyName,propertyValue); + } + configMap.put(sectionName,cameraConfig); + try + { + CONFIG_BUILDER.save(); + output = true; + } + catch(Exception e){ ErrorLogging.logError(e); } + return output; + } } diff --git a/src/main/java/org/baxter/disco/ocr/MovementFacade.java b/src/main/java/org/baxter/disco/ocr/MovementFacade.java index b6f4506..7642950 100644 --- a/src/main/java/org/baxter/disco/ocr/MovementFacade.java +++ b/src/main/java/org/baxter/disco/ocr/MovementFacade.java @@ -20,45 +20,19 @@ import com.pi4j.io.pwm.PwmType; * Currently missing Run switch compatibility. * * @author Blizzard Finnegan - * @version 2.4.0, 01 Mar. 2023 + * @version 3.0.0, 06 Mar. 2023 */ public class MovementFacade { - private static boolean exit = false; /** - * Constructor for MovementFacade. - * - * @param LOCK A Lock object, used for interactions with - * the physical lock switch on the fixture. + * Boolean used to communicate with runSwitchThread to gracefully exit. */ - //public MovementFacade(Lock LOCK) - //{ - // //ErrorLogging.logError("DEBUG: Starting lock thread..."); - // runSwitchThread = new Thread(() -> - // { - // boolean unlock = false; - // while(!exit) - // { - // if(runSwitch.isOn()) - // { - // ErrorLogging.logError("Run switch turned off!"); - // while(!LOCK.tryLock()) - // {} - // unlock = true; - // } - // else - // { - // //ErrorLogging.logError("Run switch on!"); - // if(unlock) - // { LOCK.unlock(); unlock = false; } - // } - // //try{ Thread.sleep(100); } catch(Exception e) { ErrorLogging.logError(e); } - // } - // }, "Run switch monitor."); - // runSwitchThread.start(); - // //ErrorLogging.logError("DEBUG: Lock thread started!"); - //} + private static boolean exit = false; + /** + * Thread that watches the physical Run switch on the device so that + * fixture movement can be stopped. + */ private static Thread runSwitchThread; /** @@ -74,11 +48,6 @@ public class MovementFacade */ private static final int PWM_FREQ_CONVERT = 19200; - /** - * Max allowed frequency by current fixture design. - */ - //private static int MAX_FREQUENCY = 175000; - /** * Max allowed speed by current fixture design. * Motor appears to start acting erratically over 192kHz. @@ -108,7 +77,14 @@ public class MovementFacade */ private static final int TRAVEL_DIST = 30; + /** + * What percentage of the travel to slow down the motor. + */ private static final double STEP_1 = 2/3; + + /** + * What percentage of the travel to slow down the motor farther. + */ private static final double STEP_2 = 5/6; /** @@ -116,6 +92,9 @@ public class MovementFacade */ private static double SPEED = MIN_SPEED; + /** + * Frequency fed to the PWM pin, which the motor controller converts into movement speed. + */ private static int FREQUENCY = (int)(SPEED * PWM_FREQ_CONVERT); /** @@ -254,7 +233,7 @@ public class MovementFacade if(unlock) { Cli.LOCK.unlock(); unlock = false; } } - //try{ Thread.sleep(100); } catch(Exception e) { ErrorLogging.logError(e); } + try{ Thread.sleep(100); } catch(Exception e) { ErrorLogging.logError(e); } } }, "Run switch monitor."); runSwitchThread.start(); @@ -273,11 +252,9 @@ public class MovementFacade motorDirection = outputBuilder("motorDirection", "Motor Direction", MOTOR_DIRECTION_ADDR); pistonActivate = outputBuilder("piston" , "Piston Activate", PISTON_ADDR); - //Initialise PWM object. This object is never used, - //as the PWM signal is simply a clock for the motor. + //Initialise PWM object. pwm = pwmBuilder("pwm","PWM Pin",PWM_PIN_ADDR); //pwm.on(DUTY_CYCLE, FREQUENCY); - } /** @@ -378,27 +355,26 @@ public class MovementFacade public static int resetArm() { int counter = 0; - //ErrorLogging.logError("Setting minimum frequency of PWM..."); - //ErrorLogging.logError("Minimum frequency: " + MIN_FREQUENCY); + ErrorLogging.logError("DEBUG: Setting minimum frequency of PWM..."); pwm.on(DUTY_CYCLE, MIN_FREQUENCY); if(upperLimit.isHigh()) { - //ErrorLogging.logError("Motor at highest point! Lowering to reset."); + ErrorLogging.logError("DEBUG: Motor at highest point! Lowering to reset."); motorDirection.low(); - //ErrorLogging.logError("Motor on!"); + ErrorLogging.logError("DEBUG: Motor offset on."); motorEnable.on(); try{ Thread.sleep(500); } catch (Exception e){ ErrorLogging.logError(e); } motorEnable.off(); - //ErrorLogging.logError("Motor off!"); + ErrorLogging.logError("DEBUG: Motor offset off."); } - //ErrorLogging.logError("Moving motor to highest point."); + ErrorLogging.logError("DEBUG: Moving motor to highest point."); motorDirection.high(); - //ErrorLogging.logError("Motor on!"); + ErrorLogging.logError("DEBUG: Motor return on."); motorEnable.on(); - //ErrorLogging.logError("Is the upper limit switch reached? " + upperLimit.isHigh()); + ErrorLogging.logError("DEBUG: Is the upper limit switch reached? " + upperLimit.isHigh()); while(!upperLimit.isHigh()) { try{ Thread.sleep(100); } @@ -406,7 +382,7 @@ public class MovementFacade counter++; } motorEnable.off(); - //ErrorLogging.logError("Motor returned after " + counter + " polls."); + ErrorLogging.logError("DEBUG: Motor returned after " + counter + " polls."); return counter; } @@ -418,10 +394,11 @@ public class MovementFacade ErrorLogging.logError("Initial Calibration reset."); resetArm(); ErrorLogging.logError("Coarse calibrating..."); - SPEED = calib(1, MAX_SPEED, 1); - ErrorLogging.logError("Coarse calibrating..."); + SPEED = calib(3, MAX_SPEED, 1); + ErrorLogging.logError("Fine calibrating..."); SPEED = calib(SPEED,(SPEED+1),0.1); - ErrorLogging.logError("Speed set to " + (SPEED - SPEED_BUFFER)); + ErrorLogging.logError("Calibration complete!"); + ErrorLogging.logError("DEBUG: Speed set to " + (SPEED - SPEED_BUFFER)); setSpeed(SPEED - SPEED_BUFFER); } @@ -436,39 +413,47 @@ public class MovementFacade */ private static double calib(double start, double max, double iterate) { - ErrorLogging.logError("Calibrating. Iterating from " + start + " to " + max + " in " + iterate + " steps."); + ErrorLogging.logError("DEBUG: Calibrating. Iterating from " + start + " to " + max + " in " + iterate + " steps."); + //start -= iterate; for(double i = start; i < max; i+=iterate) { - //ErrorLogging.logError("Testing speed " + i + "..."); + ErrorLogging.logError("DEBUG: Testing speed " + i + "..."); if(!setSpeed(i)) { - //ErrorLogging.logError("Speed set unsuccessfully! returning " + MIN_SPEED + "..."); + ErrorLogging.logError("DEBUG: Speed set unsuccessfully! returning " + MIN_SPEED + "..."); return MIN_SPEED; } - //ErrorLogging.logError("Motor travelling down."); + ErrorLogging.logError("DEBUG: Motor travelling down."); motorDirection.low(); - //ErrorLogging.logError("Motor on!"); + ErrorLogging.logError("DEBUG: Motor Frequency: " + FREQUENCY); + pwm.on(DUTY_CYCLE,FREQUENCY); + ErrorLogging.logError("DEBUG: Motor calibrate on."); motorEnable.on(); - for(int j = 0; j < 5; j++) + for(int j = 0; j < 20; j++) { try{ Thread.sleep(100); } catch (Exception e){ ErrorLogging.logError(e); } - if(lowerLimit.isHigh()) break; + if(lowerLimit.isHigh()) + { + ErrorLogging.logError("DEBUG: Breaking loop early!"); + break; + } } motorEnable.off(); - //ErrorLogging.logError("Motor off!"); + ErrorLogging.logError("DEBUG: Motor calibrate off."); if(upperLimit.isHigh()) { - ErrorLogging.logError("Motor faild to move! Returning " + (i - iterate)); + ErrorLogging.logError("DEBUG: Upper limit is high = " + upperLimit.isHigh()); + ErrorLogging.logError("DEBUG: Motor failed to move! Returning " + (i - iterate)); return i-iterate; } else { - //ErrorLogging.logError("Motor moved at speed " + i + ". Checking for errors."); - if(resetArm() < 10 && i > 1.0) + ErrorLogging.logError("DEBUG: Motor moved at speed " + i + ". Checking for errors."); + if(resetArm() < 10 && i > 3.0) { - ErrorLogging.logError("Motor failed to move! Returning " + (i - iterate)); + ErrorLogging.logError("DEBUG: Motor failed to move! Returning " + (i - iterate)); return i - iterate; } } @@ -496,6 +481,7 @@ public class MovementFacade } SPEED = newSpeed; FREQUENCY = (int)(SPEED * PWM_FREQ_CONVERT); + ErrorLogging.logError("DEBUG: Setting frequency to " + FREQUENCY); pwm.on(DUTY_CYCLE, FREQUENCY); return output; } @@ -504,11 +490,9 @@ public class MovementFacade * Internal function to send the fixture to a given limit switch. * * Detects if the limit switch is active before activating motor. - * - * Motor slows down after timeout is halfway through, to protect hardware. + * Motor movement is written to slow down at {@link #STEP_1} and {@link #STEP_2} * * @param moveUp Whether to send the fixture up or down. (True = up, False = down) - * @param timeout How long (in seconds) to wait before timing out. * @return true if movement was successful; otherwise false */ private static FinalState gotoLimit(boolean moveUp) @@ -536,28 +520,28 @@ public class MovementFacade int POLL_COUNT = TRAVEL_TIME * TIME_CONVERSION; int VEL_STEP_1 = (int)(STEP_1 * POLL_COUNT); int VEL_STEP_2 = (int)(STEP_2 * POLL_COUNT); - //int mostlyThere = (int) (POLL_COUNT * slowFraction); - //int slowerSpeed = (int) (FREQUENCY / speedReduceFactor); - ErrorLogging.logError("Total Poll count: " + POLL_COUNT); - ErrorLogging.logError("Transition 1: " + VEL_STEP_1); - ErrorLogging.logError("Transition 2: " + VEL_STEP_2); - ErrorLogging.logError("Travel time: " + TRAVEL_TIME); - ErrorLogging.logError("Travel speed: " + SPEED); + ErrorLogging.logError("DEBUG: Total Poll count: " + POLL_COUNT); + ErrorLogging.logError("DEBUG: Transition 1: " + VEL_STEP_1); + ErrorLogging.logError("DEBUG: Transition 2: " + VEL_STEP_2); + ErrorLogging.logError("DEBUG: Travel time: " + TRAVEL_TIME); + ErrorLogging.logError("DEBUG: Travel speed: " + SPEED); + ErrorLogging.logError("DEBUG: STEP_1: " + STEP_1); + ErrorLogging.logError("DEBUG: STEP_2: " + STEP_2); motorEnable.on(); for(int i = 0; i < (POLL_COUNT);i++) { - ErrorLogging.logError("Iteration " + i); + ErrorLogging.logError("DEBUG: Iteration " + i); try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); } if(i >= VEL_STEP_1 && i < VEL_STEP_2) { output = FinalState.UNSAFE; - ErrorLogging.logError("Slow down!"); + ErrorLogging.logError("DEBUG: Slowing down."); pwm.on(DUTY_CYCLE, FREQUENCY / 2); } else if(i >= VEL_STEP_2) { - ErrorLogging.logError("Slow down more!"); + ErrorLogging.logError("DEBUG: Slowing down more."); pwm.on(DUTY_CYCLE, FREQUENCY / 4); output = FinalState.SAFE; } diff --git a/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java b/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java index f485954..b861e43 100644 --- a/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java +++ b/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java @@ -37,7 +37,7 @@ import java.util.List; * Performs image capture, as well as image manipulation. * * @author Blizzard Finnegan - * @version 2.0.1, 15 Feb. 2023 + * @version 2.1.0, 06 Mar. 2023 */ public class OpenCVFacade { @@ -57,28 +57,35 @@ public class OpenCVFacade * Width of the image created by the camera. * !!See camera documentation before modifying!! */ - private static final int IMG_WIDTH = 3264; - //Previously used code: - //private static final int IMG_WIDTH = 800; + private static final int IMG_WIDTH = 800; /** * Height of the image created by the camera. * !!See camera documentation before modifying!! */ - private static final int IMG_HEIGHT = 2448; - //Previously used code: - //private static final int IMG_HEIGHT = 600; + private static final int IMG_HEIGHT = 600; /** * FourCC code of the image created by the camera. * !!See camera documentation before modifying!! */ private static final String CAMERA_CODEC = "mjpg"; + /** + * Name of custom-created symlink for cameras. + * This configuration must be done manually on initial install. + */ + private static final String CAMERA_FILE_PREFIX = "video-cam-"; + //Initial Camera creation static { - //Pis should already be configured to create this symlink. - newCamera("left", "/dev/video-cam1"); - newCamera("right","/dev/video-cam2"); + File devDirectory = new File("/dev"); + for(File cameraFile : devDirectory.listFiles( + (file) -> { return file.getName().contains(CAMERA_FILE_PREFIX); })) + { + String cameraName = cameraFile.getName(). + substring(CAMERA_FILE_PREFIX.length()); + newCamera(cameraName, cameraFile.getAbsolutePath()); + } } /** -- 2.47.2 From b2b05c50dfb220a7f1b95bd27f3cd659264b1c07 Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Mon, 6 Mar 2023 16:06:29 -0500 Subject: [PATCH 06/12] End of day 06 Mar. commit Remove speed from equation, attempt to debug calibration. This is the last shot at calibration. If the solution can't be found by 10am tomorrow, we revert to the old code. --- src/main/java/org/baxter/disco/ocr/Cli.java | 4 +- .../org/baxter/disco/ocr/MovementFacade.java | 57 ++++++++----------- 2 files changed, 25 insertions(+), 36 deletions(-) diff --git a/src/main/java/org/baxter/disco/ocr/Cli.java b/src/main/java/org/baxter/disco/ocr/Cli.java index 370d151..f7a9203 100644 --- a/src/main/java/org/baxter/disco/ocr/Cli.java +++ b/src/main/java/org/baxter/disco/ocr/Cli.java @@ -81,9 +81,9 @@ public class Cli ErrorLogging.logError("Version: " + version); ErrorLogging.logError("========================"); try{ - inputScanner = new Scanner(System.in); + //inputScanner = new Scanner(System.in); - ConfigFacade.init(); + //ConfigFacade.init(); //int userInput = 0; diff --git a/src/main/java/org/baxter/disco/ocr/MovementFacade.java b/src/main/java/org/baxter/disco/ocr/MovementFacade.java index 7642950..78ac930 100644 --- a/src/main/java/org/baxter/disco/ocr/MovementFacade.java +++ b/src/main/java/org/baxter/disco/ocr/MovementFacade.java @@ -52,22 +52,17 @@ public class MovementFacade * Max allowed speed by current fixture design. * Motor appears to start acting erratically over 192kHz. */ - private static final double MAX_SPEED = 10.0; + private static final int MAX_FREQUENCY = 192000; /** * Amount of buffer between the found absolute speed, and used speed. */ - private static final double SPEED_BUFFER = 0.2; + private static final int SPEED_BUFFER = 4000; /** * Minimum allowed speed of the fixture arm; also used for reset travels. */ - private static final double MIN_SPEED = 0.5; - - /** - * minimum speed, represented as frequency - */ - private static final int MIN_FREQUENCY = (int)(MIN_SPEED * PWM_FREQ_CONVERT); + private static final int MIN_FREQUENCY = 10000; /** * Distance in cm the fixture needs to travel. @@ -87,15 +82,10 @@ public class MovementFacade */ private static final double STEP_2 = 5/6; - /** - * Speed of the fixture arm, in cm/s - */ - private static double SPEED = MIN_SPEED; - /** * Frequency fed to the PWM pin, which the motor controller converts into movement speed. */ - private static int FREQUENCY = (int)(SPEED * PWM_FREQ_CONVERT); + private static int FREQUENCY = MIN_FREQUENCY; /** * PWM Duty Cycle. @@ -254,7 +244,7 @@ public class MovementFacade //Initialise PWM object. pwm = pwmBuilder("pwm","PWM Pin",PWM_PIN_ADDR); - //pwm.on(DUTY_CYCLE, FREQUENCY); + pwm.on(DUTY_CYCLE, FREQUENCY); } /** @@ -286,6 +276,7 @@ public class MovementFacade .name(name) .address(address) .pwmType(PwmType.HARDWARE) + .frequency(FREQUENCY) .provider("pigpio-pwm") .initial(1) //On program close, turn off PWM. @@ -298,6 +289,7 @@ public class MovementFacade .name(name) .address(address) .pwmType(PwmType.SOFTWARE) + .frequency(FREQUENCY) .provider("pigpio-pwm") .initial(1) //On program close, turn off PWM. @@ -394,12 +386,12 @@ public class MovementFacade ErrorLogging.logError("Initial Calibration reset."); resetArm(); ErrorLogging.logError("Coarse calibrating..."); - SPEED = calib(3, MAX_SPEED, 1); + FREQUENCY = calib(MIN_FREQUENCY, MAX_FREQUENCY, 10000); ErrorLogging.logError("Fine calibrating..."); - SPEED = calib(SPEED,(SPEED+1),0.1); + FREQUENCY = calib(FREQUENCY,(FREQUENCY+10000),1000); ErrorLogging.logError("Calibration complete!"); - ErrorLogging.logError("DEBUG: Speed set to " + (SPEED - SPEED_BUFFER)); - setSpeed(SPEED - SPEED_BUFFER); + ErrorLogging.logError("DEBUG: Speed set to " + (FREQUENCY - SPEED_BUFFER)); + setSpeed(FREQUENCY - SPEED_BUFFER); } /** @@ -411,22 +403,22 @@ public class MovementFacade * * @return The largest safe value between start and max. */ - private static double calib(double start, double max, double iterate) + private static int calib(int start, int max, int iterate) { ErrorLogging.logError("DEBUG: Calibrating. Iterating from " + start + " to " + max + " in " + iterate + " steps."); //start -= iterate; - for(double i = start; i < max; i+=iterate) + for(int i = start; i < max; i+=iterate) { ErrorLogging.logError("DEBUG: Testing speed " + i + "..."); if(!setSpeed(i)) { - ErrorLogging.logError("DEBUG: Speed set unsuccessfully! returning " + MIN_SPEED + "..."); - return MIN_SPEED; + ErrorLogging.logError("DEBUG: Speed set unsuccessfully! returning " + MIN_FREQUENCY + "..."); + return MIN_FREQUENCY; } ErrorLogging.logError("DEBUG: Motor travelling down."); motorDirection.low(); - ErrorLogging.logError("DEBUG: Motor Frequency: " + FREQUENCY); - pwm.on(DUTY_CYCLE,FREQUENCY); + //ErrorLogging.logError("DEBUG: Motor Frequency: " + FREQUENCY); + //pwm.on(DUTY_CYCLE,FREQUENCY); ErrorLogging.logError("DEBUG: Motor calibrate on."); motorEnable.on(); for(int j = 0; j < 20; j++) @@ -466,21 +458,19 @@ public class MovementFacade * * @return true if set successfully, else false */ - private static boolean setSpeed(double newSpeed) + private static boolean setSpeed(int newFrequency) { boolean output; - if(newSpeed < MIN_SPEED || newSpeed > MAX_SPEED) + if(newFrequency < MIN_FREQUENCY || newFrequency > MAX_FREQUENCY) { - SPEED = MIN_SPEED; + FREQUENCY = MIN_FREQUENCY; output = false; } else { - SPEED = newSpeed; + FREQUENCY = newFrequency; output = true; } - SPEED = newSpeed; - FREQUENCY = (int)(SPEED * PWM_FREQ_CONVERT); ErrorLogging.logError("DEBUG: Setting frequency to " + FREQUENCY); pwm.on(DUTY_CYCLE, FREQUENCY); return output; @@ -514,9 +504,8 @@ public class MovementFacade if(limitSense.isHigh()) return FinalState.SAFE; - FREQUENCY = (int)(SPEED * TIME_CONVERSION); pwm.on(DUTY_CYCLE, FREQUENCY); - int TRAVEL_TIME = (int)(TRAVEL_DIST / SPEED); + int TRAVEL_TIME = (int)(TRAVEL_DIST / (FREQUENCY / PWM_FREQ_CONVERT)); int POLL_COUNT = TRAVEL_TIME * TIME_CONVERSION; int VEL_STEP_1 = (int)(STEP_1 * POLL_COUNT); int VEL_STEP_2 = (int)(STEP_2 * POLL_COUNT); @@ -525,7 +514,7 @@ public class MovementFacade ErrorLogging.logError("DEBUG: Transition 1: " + VEL_STEP_1); ErrorLogging.logError("DEBUG: Transition 2: " + VEL_STEP_2); ErrorLogging.logError("DEBUG: Travel time: " + TRAVEL_TIME); - ErrorLogging.logError("DEBUG: Travel speed: " + SPEED); + ErrorLogging.logError("DEBUG: Travel speed: " + (FREQUENCY/PWM_FREQ_CONVERT)); ErrorLogging.logError("DEBUG: STEP_1: " + STEP_1); ErrorLogging.logError("DEBUG: STEP_2: " + STEP_2); motorEnable.on(); -- 2.47.2 From 7708d1359a1e1823068159c51e2e118e01a8e25c Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Tue, 7 Mar 2023 13:58:45 -0500 Subject: [PATCH 07/12] Update docs, rollback accidental version increment ErrorLogging now has consistent indentation MovementFacade needs to be rolled back --- README.md | 2 +- dependency-reduced-pom.xml | 2 +- pom.xml | 2 +- src/main/java/org/baxter/disco/ocr/Cli.java | 36 ++++- .../org/baxter/disco/ocr/ErrorLogging.java | 4 +- .../org/baxter/disco/ocr/MovementFacade.java | 145 ++++++------------ toDoList.md | 18 +-- 7 files changed, 87 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index 8fa8c49..2623cac 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This is a personal/professional project, which makes use of JavaCV, OpenCV, Tess ## Known Bugs -- As of v4.1.0, cropping images is done by a temporary window created by OpenCV. This window will crash after being used correctly. This can be safely killed when prompted, without affecting the rest of the project. Investigation into how to fix this crashing is ongoing, but it has not yet been resolved. +- As of v4.1.0, cropping images is done by a temporary window created by OpenCV. This window does not close itself, but is re-used if a new cropping region is requested. This can be safely killed when prompted, without affecting the rest of the project. ## Dependencies diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index 546df28..bb421ef 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -4,7 +4,7 @@ org.baxter.disco ocr Disco OCR Accuracy Over Life Testing - 4.3.6 + 4.3.5 Testing Discos for long-term accuracy, using automated optical character recognition. Baxter International diff --git a/pom.xml b/pom.xml index dee8341..af94a17 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.baxter.disco ocr - 4.3.6 + 4.3.5 jar Disco OCR Accuracy Over Life Testing Testing Discos for long-term accuracy, using automated optical character recognition. diff --git a/src/main/java/org/baxter/disco/ocr/Cli.java b/src/main/java/org/baxter/disco/ocr/Cli.java index f7a9203..4e3d4cf 100644 --- a/src/main/java/org/baxter/disco/ocr/Cli.java +++ b/src/main/java/org/baxter/disco/ocr/Cli.java @@ -25,7 +25,7 @@ public class Cli /** * Complete build version number */ - private static final String version = "4.3.6"; + private static final String version = "4.3.5"; /** * Currently saved iteration count. @@ -81,14 +81,40 @@ public class Cli ErrorLogging.logError("Version: " + version); ErrorLogging.logError("========================"); try{ - //inputScanner = new Scanner(System.in); + inputScanner = new Scanner(System.in); //ConfigFacade.init(); - //int userInput = 0; + int userInput = 0; - ErrorLogging.logError("Initialising fixture movement. This will take a few moments, and may produce some temporary unnerving sounds from the motor. This is expected behaviour, and no damage is being done."); - MovementFacade.calibrate(); + MovementFacade.resetArm(); + MovementFacade.goDown(); + MovementFacade.goUp(); + + prompt("Did the motor move successfully? (Y/n): "); + String successfulMovement = inputScanner.next().trim().toLowerCase(); + ErrorLogging.logError("DEBUG: user report of motor movement = " + successfulMovement); + if(successfulMovement == "" || successfulMovement.charAt(0) != 'y') + { + ErrorLogging.logError("DEBUG: User reported failed movement of the motor! Allowing reset of frequency..."); + print("As the default frequency did not work successfully, a new frequency value is required."); + print("Higher frequency: Faster motor movement, not guaranteed to work, may cause damage to motor."); + print(" Lower frequency: Slower motor movement, safer, but longer iteration time."); + boolean frequencyReset = false; + while(true) + { + prompt("Please enter a new frequency value (Default: 50kHz): "); + userInput = (int)inputFiltering(inputScanner.next()); + frequencyReset = MovementFacade.setFrequency(userInput); + if(!frequencyReset) continue; + MovementFacade.resetArm(); + MovementFacade.goDown(); + MovementFacade.goUp(); + prompt("Did the motor move successfully? (Y/n): "); + successfulMovement = inputScanner.next(); + if(successfulMovement != "" || successfulMovement.toLowerCase().charAt(0) != 'n') break; + } + } //do //{ diff --git a/src/main/java/org/baxter/disco/ocr/ErrorLogging.java b/src/main/java/org/baxter/disco/ocr/ErrorLogging.java index 37bcf16..0384064 100644 --- a/src/main/java/org/baxter/disco/ocr/ErrorLogging.java +++ b/src/main/java/org/baxter/disco/ocr/ErrorLogging.java @@ -18,7 +18,7 @@ import org.apache.commons.lang3.exception.ExceptionUtils; * as well as stderr. * * @author Blizzard Finnegan - * @version 1.3.1, 09 Feb. 2023 + * @version 1.3.2, 07 Mar. 2023 */ public class ErrorLogging @@ -109,7 +109,7 @@ public class ErrorLogging */ public static void logError(String error) { - String errorMessage = datetime.format(LocalDateTime.now()) + " - " + error; + String errorMessage = datetime.format(LocalDateTime.now()) + "\t- " + error; fileOut.println(errorMessage); fileOut.flush(); if(!error.substring(0,5).equals("DEBUG")) diff --git a/src/main/java/org/baxter/disco/ocr/MovementFacade.java b/src/main/java/org/baxter/disco/ocr/MovementFacade.java index 78ac930..8728ff8 100644 --- a/src/main/java/org/baxter/disco/ocr/MovementFacade.java +++ b/src/main/java/org/baxter/disco/ocr/MovementFacade.java @@ -46,23 +46,18 @@ public class MovementFacade * * Frequency (Hz) = Speed (cm/s) * (pulses/rev) * (lead) */ - private static final int PWM_FREQ_CONVERT = 19200; + private static final double PWM_FREQ_CONVERT = 19200; /** * Max allowed speed by current fixture design. * Motor appears to start acting erratically over 192kHz. */ - private static final int MAX_FREQUENCY = 192000; - - /** - * Amount of buffer between the found absolute speed, and used speed. - */ - private static final int SPEED_BUFFER = 4000; + private static final double MAX_FREQUENCY = 192000; /** * Minimum allowed speed of the fixture arm; also used for reset travels. */ - private static final int MIN_FREQUENCY = 10000; + private static final double MIN_FREQUENCY = 20000; /** * Distance in cm the fixture needs to travel. @@ -70,22 +65,22 @@ public class MovementFacade * Distance between limit switches: ~80cm. * Thickness of fixture arm: ~50cm. */ - private static final int TRAVEL_DIST = 30; + private static final int TRAVEL_DIST = 10; /** * What percentage of the travel to slow down the motor. */ - private static final double STEP_1 = 2/3; + private static final double STEP_1 = 1.0 / 2.0; /** * What percentage of the travel to slow down the motor farther. */ - private static final double STEP_2 = 5/6; + private static final double STEP_2 = 3.0 / 4.0; /** - * Frequency fed to the PWM pin, which the motor controller converts into movement speed. + * Default frequency fed to the PWM pin, which the motor controller converts into movement speed. */ - private static int FREQUENCY = MIN_FREQUENCY; + private static int FREQUENCY = 70000; /** * PWM Duty Cycle. @@ -348,7 +343,7 @@ public class MovementFacade { int counter = 0; ErrorLogging.logError("DEBUG: Setting minimum frequency of PWM..."); - pwm.on(DUTY_CYCLE, MIN_FREQUENCY); + pwm.on(DUTY_CYCLE, (int)MIN_FREQUENCY); if(upperLimit.isHigh()) { ErrorLogging.logError("DEBUG: Motor at highest point! Lowering to reset."); @@ -378,92 +373,18 @@ public class MovementFacade return counter; } - /** - * Used to set the motor's max speed. - */ - public static void calibrate() - { - ErrorLogging.logError("Initial Calibration reset."); - resetArm(); - ErrorLogging.logError("Coarse calibrating..."); - FREQUENCY = calib(MIN_FREQUENCY, MAX_FREQUENCY, 10000); - ErrorLogging.logError("Fine calibrating..."); - FREQUENCY = calib(FREQUENCY,(FREQUENCY+10000),1000); - ErrorLogging.logError("Calibration complete!"); - ErrorLogging.logError("DEBUG: Speed set to " + (FREQUENCY - SPEED_BUFFER)); - setSpeed(FREQUENCY - SPEED_BUFFER); - } - - /** - * Find the max speed of the fixure between two points. - * - * @param start Lowest speed to check - * @param max Highest speed to check - * @param iterate How much to iterate by - * - * @return The largest safe value between start and max. - */ - private static int calib(int start, int max, int iterate) - { - ErrorLogging.logError("DEBUG: Calibrating. Iterating from " + start + " to " + max + " in " + iterate + " steps."); - //start -= iterate; - for(int i = start; i < max; i+=iterate) - { - ErrorLogging.logError("DEBUG: Testing speed " + i + "..."); - if(!setSpeed(i)) - { - ErrorLogging.logError("DEBUG: Speed set unsuccessfully! returning " + MIN_FREQUENCY + "..."); - return MIN_FREQUENCY; - } - ErrorLogging.logError("DEBUG: Motor travelling down."); - motorDirection.low(); - //ErrorLogging.logError("DEBUG: Motor Frequency: " + FREQUENCY); - //pwm.on(DUTY_CYCLE,FREQUENCY); - ErrorLogging.logError("DEBUG: Motor calibrate on."); - motorEnable.on(); - for(int j = 0; j < 20; j++) - { - try{ Thread.sleep(100); } - catch (Exception e){ ErrorLogging.logError(e); } - if(lowerLimit.isHigh()) - { - ErrorLogging.logError("DEBUG: Breaking loop early!"); - break; - } - } - motorEnable.off(); - ErrorLogging.logError("DEBUG: Motor calibrate off."); - if(upperLimit.isHigh()) - { - ErrorLogging.logError("DEBUG: Upper limit is high = " + upperLimit.isHigh()); - ErrorLogging.logError("DEBUG: Motor failed to move! Returning " + (i - iterate)); - return i-iterate; - } - else - { - - ErrorLogging.logError("DEBUG: Motor moved at speed " + i + ". Checking for errors."); - if(resetArm() < 10 && i > 3.0) - { - ErrorLogging.logError("DEBUG: Motor failed to move! Returning " + (i - iterate)); - return i - iterate; - } - } - } - return max-iterate; - } - /** * Safely set the speed of the motor and fixture. * * @return true if set successfully, else false */ - private static boolean setSpeed(int newFrequency) + public static boolean setFrequency(int newFrequency) { boolean output; if(newFrequency < MIN_FREQUENCY || newFrequency > MAX_FREQUENCY) { - FREQUENCY = MIN_FREQUENCY; + ErrorLogging.logError("Movement control error!!! - Invalid frequency input."); + FREQUENCY = (int)MIN_FREQUENCY; output = false; } else @@ -493,13 +414,13 @@ public class MovementFacade { motorDirection.high(); limitSense = upperLimit; - ErrorLogging.logError("Sending fixture up..."); + ErrorLogging.logError("DEBUG: Sending fixture up..."); } else { motorDirection.low(); limitSense = lowerLimit; - ErrorLogging.logError("Sending fixture down..."); + ErrorLogging.logError("DEBUG: Sending fixture down..."); } if(limitSense.isHigh()) return FinalState.SAFE; @@ -510,35 +431,57 @@ public class MovementFacade int VEL_STEP_1 = (int)(STEP_1 * POLL_COUNT); int VEL_STEP_2 = (int)(STEP_2 * POLL_COUNT); + ErrorLogging.logError("DEBUG: ========================================"); ErrorLogging.logError("DEBUG: Total Poll count: " + POLL_COUNT); ErrorLogging.logError("DEBUG: Transition 1: " + VEL_STEP_1); ErrorLogging.logError("DEBUG: Transition 2: " + VEL_STEP_2); ErrorLogging.logError("DEBUG: Travel time: " + TRAVEL_TIME); ErrorLogging.logError("DEBUG: Travel speed: " + (FREQUENCY/PWM_FREQ_CONVERT)); - ErrorLogging.logError("DEBUG: STEP_1: " + STEP_1); - ErrorLogging.logError("DEBUG: STEP_2: " + STEP_2); + ErrorLogging.logError("DEBUG: ========================================"); + + ErrorLogging.logError("DEBUG: Motor on."); motorEnable.on(); + boolean slow = false; + boolean slower = false; for(int i = 0; i < (POLL_COUNT);i++) { - ErrorLogging.logError("DEBUG: Iteration " + i); try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); } + if(limitSense.isOn()) + { + ErrorLogging.logError("DEBUG: Exiting early due to limit switch activation!"); + ErrorLogging.logError("DEBUG: Exiting on poll " + i); + break; + } if(i >= VEL_STEP_1 && i < VEL_STEP_2) { output = FinalState.UNSAFE; - ErrorLogging.logError("DEBUG: Slowing down."); - pwm.on(DUTY_CYCLE, FREQUENCY / 2); + if(slow) + { + ErrorLogging.logError("DEBUG: Slowing down."); + slow = true; + } + pwm.on(DUTY_CYCLE, (int)(FREQUENCY / 1.25)); } else if(i >= VEL_STEP_2) { - ErrorLogging.logError("DEBUG: Slowing down more."); - pwm.on(DUTY_CYCLE, FREQUENCY / 4); output = FinalState.SAFE; + if(slower) + { + ErrorLogging.logError("DEBUG: Slowing down more."); + slower = true; + } + pwm.on(DUTY_CYCLE, FREQUENCY / 2); } } motorEnable.off(); + ErrorLogging.logError("DEBUG: Motor off."); if(output == FinalState.FAILED) - ErrorLogging.logError("FIXTURE MOVEMENT ERROR! - Motor movement timed out!"); + ErrorLogging.logError("DEBUG: Motor movement exited early."); + else if(output == FinalState.UNSAFE) + ErrorLogging.logError("DEBUG: Motor movement slowed down, but not fully."); + else if(output == FinalState.SAFE) + ErrorLogging.logError("DEBUG: Motor movement successfully slowed fully."); pwm.on(DUTY_CYCLE, FREQUENCY); return output; } diff --git a/toDoList.md b/toDoList.md index 9e06829..727b5ae 100644 --- a/toDoList.md +++ b/toDoList.md @@ -2,18 +2,15 @@ ## High-priority fixes -## Cli(?) +### DataSaving -- [x] enable camera toggling +- [ ] Modify initialisation to create formula cells outside data lines + - Will need to evaluate functions on every line write ### OpenCVFacade - [ ] completeProcess should have more robust file output checking -### Gui - -- [ ] Debug and ensure GUI is written properly - ## Low-priority improvements ### All @@ -22,13 +19,12 @@ - [ ] reduce Javadoc linking to one link per reference per class - [ ] Use generated Javadocs to improve Javadocs (perpetual) -### Cli +### Gui -- [ ] Find way to kill CanvasFrame protection Thread on exit +- [ ] Debug and ensure GUI is written properly ### TesseractFacade -- [ ] parse text-based input? - - requires further communication with Pete to determine if necessary. - - requires retraining Tesseract +- [x] parse text-based input? + - Determined to be unnecessary, as further data filtering is all that is required. -- 2.47.2 From 3167df31922d2c00bf58db19864c1c4997293ac7 Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Tue, 7 Mar 2023 15:41:10 -0500 Subject: [PATCH 08/12] End of day 07 Mar. commit CLI is now back to normal, updated DataSaving to be better. Logging now stores camera names. --- src/main/java/org/baxter/disco/ocr/Cli.java | 144 +++++++---------- .../java/org/baxter/disco/ocr/DataSaving.java | 90 ++++------- .../org/baxter/disco/ocr/MovementFacade.java | 149 +++++++++++++----- .../org/baxter/disco/ocr/OpenCVFacade.java | 1 + 4 files changed, 200 insertions(+), 184 deletions(-) diff --git a/src/main/java/org/baxter/disco/ocr/Cli.java b/src/main/java/org/baxter/disco/ocr/Cli.java index 4e3d4cf..a1b869d 100644 --- a/src/main/java/org/baxter/disco/ocr/Cli.java +++ b/src/main/java/org/baxter/disco/ocr/Cli.java @@ -83,99 +83,73 @@ public class Cli try{ inputScanner = new Scanner(System.in); - //ConfigFacade.init(); + ConfigFacade.init(); int userInput = 0; - MovementFacade.resetArm(); - MovementFacade.goDown(); - MovementFacade.goUp(); + ErrorLogging.logError("Calibrating motor movement. This may take several minutes..."); + MovementFacade.calibrate(); - prompt("Did the motor move successfully? (Y/n): "); - String successfulMovement = inputScanner.next().trim().toLowerCase(); - ErrorLogging.logError("DEBUG: user report of motor movement = " + successfulMovement); - if(successfulMovement == "" || successfulMovement.charAt(0) != 'y') + do { - ErrorLogging.logError("DEBUG: User reported failed movement of the motor! Allowing reset of frequency..."); - print("As the default frequency did not work successfully, a new frequency value is required."); - print("Higher frequency: Faster motor movement, not guaranteed to work, may cause damage to motor."); - print(" Lower frequency: Slower motor movement, safer, but longer iteration time."); - boolean frequencyReset = false; - while(true) + printMainMenu(); + userInput = (int)inputFiltering(inputScanner.nextLine()); + + switch (userInput) { - prompt("Please enter a new frequency value (Default: 50kHz): "); - userInput = (int)inputFiltering(inputScanner.next()); - frequencyReset = MovementFacade.setFrequency(userInput); - if(!frequencyReset) continue; - MovementFacade.resetArm(); - MovementFacade.goDown(); - MovementFacade.goUp(); - prompt("Did the motor move successfully? (Y/n): "); - successfulMovement = inputScanner.next(); - if(successfulMovement != "" || successfulMovement.toLowerCase().charAt(0) != 'n') break; - } - } + case 1: + println("Setting up cameras..."); + println("This may take a moment..."); + configureCameras(); + camerasConfigured = true; + break; + case 2: + setDUTSerials(); + break; + case 3: + setIterationCount(); + break; + case 4: + setActiveCameras(); + break; + case 5: + if(!camerasConfigured) + { + prompt("You have not configured the cameras yet! Are you sure you would like to continue? (y/N): "); + String input = inputScanner.nextLine().toLowerCase().trim(); + if( input.isBlank() || input.charAt(0) != 'y' ) break; + else + ErrorLogging.logError("DEBUG: Potential for error: Un-initialised cameras."); + } - //do - //{ - // printMainMenu(); - // userInput = (int)inputFiltering(inputScanner.nextLine()); + serialsSet = true; + for(String cameraName : OpenCVFacade.getCameraNames()) + { + if(ConfigFacade.getValue(cameraName,ConfigProperties.ACTIVE) != 0 && + ConfigFacade.getSerial(cameraName) == null ) + serialsSet = false; + } + if(!serialsSet) + { + prompt("You have not set the serial numbers for your DUTs yet! Are you sure you would like to continue? (y/N): "); + String input = inputScanner.nextLine().toLowerCase().trim(); + if( input.isBlank() || input.charAt(0) != 'y' ) break; + else + ErrorLogging.logError("DEBUG: Potential for error: Un-initialised DUT Serial numbers."); + } - // switch (userInput) - // { - // case 1: - // println("Setting up cameras..."); - // println("This may take a moment..."); - // configureCameras(); - // camerasConfigured = true; - // break; - // case 2: - // setDUTSerials(); - // break; - // case 3: - // setIterationCount(); - // break; - // case 4: - // setActiveCameras(); - // break; - // case 5: - // if(!camerasConfigured) - // { - // prompt("You have not configured the cameras yet! Are you sure you would like to continue? (y/N): "); - // String input = inputScanner.nextLine().toLowerCase().trim(); - // if( input.isBlank() || input.charAt(0) != 'y' ) break; - // else - // ErrorLogging.logError("DEBUG: Potential for error: Un-initialised cameras."); - // } + runTests(); + break; + case 6: + printHelp(); + break; + case 8: + break; + default: + //Input handling already done by inputFiltering() + } - // serialsSet = true; - // for(String cameraName : OpenCVFacade.getCameraNames()) - // { - // if(ConfigFacade.getValue(cameraName,ConfigProperties.ACTIVE) != 0 && - // ConfigFacade.getSerial(cameraName) == null ) - // serialsSet = false; - // } - // if(!serialsSet) - // { - // prompt("You have not set the serial numbers for your DUTs yet! Are you sure you would like to continue? (y/N): "); - // String input = inputScanner.nextLine().toLowerCase().trim(); - // if( input.isBlank() || input.charAt(0) != 'y' ) break; - // else - // ErrorLogging.logError("DEBUG: Potential for error: Un-initialised DUT Serial numbers."); - // } - - // runTests(); - // break; - // case 6: - // printHelp(); - // break; - // case 8: - // break; - // default: - // //Input handling already done by inputFiltering() - // } - - //} while (userInput != mainMenuOptionCount); + } while (userInput != mainMenuOptionCount); } //If anything ever goes wrong, catch the error and exit @@ -693,8 +667,6 @@ public class Cli //DO NOT CLEAR camera to file Map. This will change the order of the objects within it resultMap.clear(); } - //Close the Excel workbook - DataSaving.closeWorkbook(cameraList.size()); //Alert the user to testing being complete println("======================================="); println("Testing complete!"); diff --git a/src/main/java/org/baxter/disco/ocr/DataSaving.java b/src/main/java/org/baxter/disco/ocr/DataSaving.java index 56aaadb..df92e94 100644 --- a/src/main/java/org/baxter/disco/ocr/DataSaving.java +++ b/src/main/java/org/baxter/disco/ocr/DataSaving.java @@ -32,7 +32,7 @@ import static org.apache.poi.hssf.util.HSSFColor.HSSFColorPredefined; * Facade for saving data out to a file. * * @author Blizzard Finnegan - * @version 4.1.0, 24 Feb. 2023 + * @version 5.0.0, 07 Mar. 2023 */ public class DataSaving { @@ -148,6 +148,10 @@ public class DataSaving cell = row.createCell(cellnum++); cell.setCellValue(""); } + HSSFCell serialTitleCell = row.createCell(cellnum++); + serialTitleCell.setCellValue("Serial"); + HSSFCell passPercentCell = row.createCell(cellnum++); + passPercentCell.setCellValue("Pass %"); //Save to file try (FileOutputStream outputStream = new FileOutputStream(outputFile)) @@ -163,44 +167,44 @@ public class DataSaving * * @param cameraCount The number of cameras that were used. */ - public static void closeWorkbook(int cameraCount) + private static void updateFormulas(int cameraCount) { + int rowIndex = 0; + FormulaEvaluator formulaEvaluator = outputWorkbook.getCreationHelper().createFormulaEvaluator(); + int lastColumnOfData = outputSheet.getRow(rowIndex).getLastCellNum(); + int serialColumn = lastColumnOfData + 1; + int percentColumn = lastColumnOfData + 2; + //Get the last row, add another row below it, and name the first cell "Totals:" int lastRowOfData = outputSheet.getLastRowNum(); - HSSFRow finalRow = outputSheet.createRow(++lastRowOfData); - HSSFCell titleCell = finalRow.createCell(0); - titleCell.setCellValue("Totals:"); - //ErrorLogging.logError("DEBUG: 3 ?= " + (cameraCount*3)); - //For each camera, create a unique total line for(int column = 3; column <= (cameraCount*3); column+=3) { - //Create a cell in the right column - HSSFCell cell = finalRow.createCell(column); - FormulaEvaluator formulaEvaluator = outputWorkbook.getCreationHelper().createFormulaEvaluator(); String columnName = CellReference.convertNumToColString(column); - - //Intermediate variable to store the array of possible values - //Dollar signs in use here indicate that if the cell is copied and pasted, - //the same cells will be referenced + HSSFRow row = outputSheet.getRow(++rowIndex); + HSSFCell serialCell = row.createCell(serialColumn); + String formula = "$" + columnName + "$" + rowIndex; + serialCell.setCellFormula(formula); + formulaEvaluator.evaluate(serialCell); + + HSSFCell percentCell = row.createCell(percentColumn); String verticalArray = String.format("$%s$2:$%s$%s",columnName,columnName,lastRowOfData); ErrorLogging.logError("DEBUG: Vertical Array: " + verticalArray); - //Create the error formula, then set it as the cell's formula. - String formula = String.format( - "(COUNT(%s)-COUNTIF(%s,{\"<%s\",\">%s\"}))/(COUNT(%s))", + formula = String.format( + "(COUNT(%s)-(COUNTIF(%s,\"<%s\")+COUNTIF(%s,\">%s\",))/(COUNT(%s))", verticalArray, - verticalArray, (targetTemp - failRange), (targetTemp + failRange), + verticalArray, (targetTemp - failRange), + verticalArray, (targetTemp + failRange), verticalArray); - cell.setCellFormula(formula); + percentCell.setCellFormula(formula); - //Make the percentage human-readable - cell.setCellStyle(finalValuesStyle); + percentCell.setCellStyle(finalValuesStyle); - //To make the cell be a readable value, you need to - //evaluate the formula within the cell. - formulaEvaluator.evaluate(cell); + //To make the percentCell be a readable value, you need to + //evaluate the formula within the percentCell. + formulaEvaluator.evaluate(percentCell); ErrorLogging.logError("DEBUG: Formula: " + formula); } @@ -282,44 +286,8 @@ public class DataSaving //Create a blank cell as a spacer row.createCell(cellnum++); - //ErrorLogging.logError("DEBUG: " + cameraName); - - //objectArray.add(serialNumber); - //objectArray.add(file.getPath()); - //objectArray.add(inputMap.get(file)); - //objectArray.add(" "); } - //for(Object cellObject : objectArray) - //{ - // HSSFCell cell = row.createCell(cellnum++); - // if(cellObject instanceof Double) - // { - // Double cellValue = (Double)cellObject; - // ErrorLogging.logError("DEBUG: " + cellValue + " ?= " + targetTemp + " +- " + failRange); - // if(cellValue.equals(Double.NEGATIVE_INFINITY)) - // { - // cell.setCellValue("ERROR!"); - // cell.setCellStyle(errorStyle); - // } - // else - // { - // cell.setCellValue(cellValue); - // if( cellValue.doubleValue() > (targetTemp + failRange) || - // cellValue.doubleValue() < (targetTemp - failRange) ) - // { - // ErrorLogging.logError("DEBUG: Cell value " + cellValue.doubleValue() + " is outside the allowed range! (" + (targetTemp -failRange) + "-" + (targetTemp + failRange) + "). Setting cell to fail colouring."); - // cell.setCellStyle(failStyle); - // } - // } - // } - // else if(cellObject instanceof String) cell.setCellValue((String) cellObject); - // else - // { - // ErrorLogging.logError("XLSX Write Error!!! - Invalid input."); - // ErrorLogging.logError("\t" + cellObject.toString()); - // } - - //} + updateFormulas(cameraNames.size()); try (FileOutputStream outputStream = new FileOutputStream(outputFile)) { outputWorkbook.write(outputStream); output = true; } catch(Exception e) {ErrorLogging.logError(e);} diff --git a/src/main/java/org/baxter/disco/ocr/MovementFacade.java b/src/main/java/org/baxter/disco/ocr/MovementFacade.java index 8728ff8..8d112cf 100644 --- a/src/main/java/org/baxter/disco/ocr/MovementFacade.java +++ b/src/main/java/org/baxter/disco/ocr/MovementFacade.java @@ -52,35 +52,44 @@ public class MovementFacade * Max allowed speed by current fixture design. * Motor appears to start acting erratically over 192kHz. */ - private static final double MAX_FREQUENCY = 192000; + private static final int MAX_FREQUENCY = 192000; + + /** + * Amount of buffer between the found absolute speed, and used speed. + */ + private static final int SPEED_BUFFER = 4000; /** * Minimum allowed speed of the fixture arm; also used for reset travels. */ - private static final double MIN_FREQUENCY = 20000; + private static final int MIN_FREQUENCY = 10000; /** * Distance in cm the fixture needs to travel. * * Distance between limit switches: ~80cm. * Thickness of fixture arm: ~50cm. + * *** ABOVE MATH IS WRONG *** + * I don't know what I did wrong, but this value doesn't work + * properly if straight calculated. + * I will look into this more at some point. */ - private static final int TRAVEL_DIST = 10; + private static final int TRAVEL_DIST = 12; /** * What percentage of the travel to slow down the motor. */ - private static final double STEP_1 = 1.0 / 2.0; + private static final double STEP_1 = 2.0/3.0; /** * What percentage of the travel to slow down the motor farther. */ - private static final double STEP_2 = 3.0 / 4.0; + private static final double STEP_2 = 5.0/6.0; /** - * Default frequency fed to the PWM pin, which the motor controller converts into movement speed. + * Frequency fed to the PWM pin, which the motor controller converts into movement speed. */ - private static int FREQUENCY = 70000; + private static int FREQUENCY = MIN_FREQUENCY; /** * PWM Duty Cycle. @@ -341,9 +350,10 @@ public class MovementFacade */ public static int resetArm() { + ErrorLogging.logError("DEBUG: --------------------------------------"); int counter = 0; ErrorLogging.logError("DEBUG: Setting minimum frequency of PWM..."); - pwm.on(DUTY_CYCLE, (int)MIN_FREQUENCY); + pwm.on(DUTY_CYCLE, MIN_FREQUENCY); if(upperLimit.isHigh()) { ErrorLogging.logError("DEBUG: Motor at highest point! Lowering to reset."); @@ -370,21 +380,94 @@ public class MovementFacade } motorEnable.off(); ErrorLogging.logError("DEBUG: Motor returned after " + counter + " polls."); + ErrorLogging.logError("DEBUG: --------------------------------------"); return counter; } + /** + * Used to set the motor's max speed. + */ + public static void calibrate() + { + ErrorLogging.logError("Initial Calibration reset."); + resetArm(); + ErrorLogging.logError("Coarse calibrating..."); + FREQUENCY = calib(MIN_FREQUENCY, MAX_FREQUENCY, 10000); + ErrorLogging.logError("Fine calibrating..."); + FREQUENCY = calib(FREQUENCY,(FREQUENCY+10000),1000); + ErrorLogging.logError("Calibration complete!"); + ErrorLogging.logError("DEBUG: Speed set to " + (FREQUENCY - SPEED_BUFFER)); + setFrequency(FREQUENCY - SPEED_BUFFER); + } + + /** + * Find the max speed of the fixure between two points. + * + * @param start Lowest speed to check + * @param max Highest speed to check + * @param iterate How much to iterate by + * + * @return The largest safe value between start and max. + */ + private static int calib(int start, int max, int iterate) + { + //start -= iterate; + for(int i = start; i < max; i+=iterate) + { + if(!setFrequency(i)) + { + ErrorLogging.logError("DEBUG: Speed set unsuccessfully! returning " + MIN_FREQUENCY + "..."); + return MIN_FREQUENCY; + } + ErrorLogging.logError("DEBUG: Motor travelling down."); + motorDirection.low(); + ErrorLogging.logError("DEBUG: Motor Frequency: " + FREQUENCY); + ErrorLogging.logError("DEBUG: Motor calibrate on."); + motorEnable.on(); + for(int j = 0; j < 20; j++) + { + try{ Thread.sleep(100); } + catch (Exception e){ ErrorLogging.logError(e); } + if(lowerLimit.isHigh()) + { + ErrorLogging.logError("DEBUG: Breaking loop early!"); + break; + } + } + motorEnable.off(); + ErrorLogging.logError("DEBUG: Motor calibrate off."); + if(upperLimit.isHigh()) + { + ErrorLogging.logError("DEBUG: Upper limit is high = " + upperLimit.isHigh()); + ErrorLogging.logError("DEBUG: Motor failed to move! Returning " + (i - iterate)); + return i-iterate; + } + else + { + + ErrorLogging.logError("DEBUG: Motor moved at speed " + i + ". Checking for errors."); + if(resetArm() < 10 && i > 3.0) + { + ErrorLogging.logError("DEBUG: Motor failed to move! Returning " + (i - iterate)); + return i - iterate; + } + } + } + return max-iterate; + } + /** * Safely set the speed of the motor and fixture. * * @return true if set successfully, else false */ - public static boolean setFrequency(int newFrequency) + private static boolean setFrequency(int newFrequency) { boolean output; if(newFrequency < MIN_FREQUENCY || newFrequency > MAX_FREQUENCY) { - ErrorLogging.logError("Movement control error!!! - Invalid frequency input."); - FREQUENCY = (int)MIN_FREQUENCY; + ErrorLogging.logError("DEBUG: Invalid MovementFacade.setFrequency() value, setting to minfrequency!"); + FREQUENCY = MIN_FREQUENCY; output = false; } else @@ -414,13 +497,13 @@ public class MovementFacade { motorDirection.high(); limitSense = upperLimit; - ErrorLogging.logError("DEBUG: Sending fixture up..."); + ErrorLogging.logError("Sending fixture up..."); } else { motorDirection.low(); limitSense = lowerLimit; - ErrorLogging.logError("DEBUG: Sending fixture down..."); + ErrorLogging.logError("Sending fixture down..."); } if(limitSense.isHigh()) return FinalState.SAFE; @@ -431,57 +514,49 @@ public class MovementFacade int VEL_STEP_1 = (int)(STEP_1 * POLL_COUNT); int VEL_STEP_2 = (int)(STEP_2 * POLL_COUNT); - ErrorLogging.logError("DEBUG: ========================================"); + ErrorLogging.logError("DEBUG: ================================="); ErrorLogging.logError("DEBUG: Total Poll count: " + POLL_COUNT); ErrorLogging.logError("DEBUG: Transition 1: " + VEL_STEP_1); ErrorLogging.logError("DEBUG: Transition 2: " + VEL_STEP_2); ErrorLogging.logError("DEBUG: Travel time: " + TRAVEL_TIME); ErrorLogging.logError("DEBUG: Travel speed: " + (FREQUENCY/PWM_FREQ_CONVERT)); - ErrorLogging.logError("DEBUG: ========================================"); - - ErrorLogging.logError("DEBUG: Motor on."); + ErrorLogging.logError("DEBUG: STEP_1: " + STEP_1); + ErrorLogging.logError("DEBUG: STEP_2: " + STEP_2); + ErrorLogging.logError("DEBUG: ================================="); motorEnable.on(); - boolean slow = false; - boolean slower = false; for(int i = 0; i < (POLL_COUNT);i++) { try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); } - if(limitSense.isOn()) - { - ErrorLogging.logError("DEBUG: Exiting early due to limit switch activation!"); - ErrorLogging.logError("DEBUG: Exiting on poll " + i); - break; - } if(i >= VEL_STEP_1 && i < VEL_STEP_2) { - output = FinalState.UNSAFE; - if(slow) + if(output != FinalState.UNSAFE) { + output = FinalState.UNSAFE; ErrorLogging.logError("DEBUG: Slowing down."); - slow = true; + pwm.on(DUTY_CYCLE, (int)(FREQUENCY / 1.25)); } - pwm.on(DUTY_CYCLE, (int)(FREQUENCY / 1.25)); } else if(i >= VEL_STEP_2) { - output = FinalState.SAFE; - if(slower) + if(output != FinalState.SAFE) { ErrorLogging.logError("DEBUG: Slowing down more."); - slower = true; + pwm.on(DUTY_CYCLE, FREQUENCY / 2); + output = FinalState.SAFE; } - pwm.on(DUTY_CYCLE, FREQUENCY / 2); } } motorEnable.off(); - ErrorLogging.logError("DEBUG: Motor off."); if(output == FinalState.FAILED) - ErrorLogging.logError("DEBUG: Motor movement exited early."); + ErrorLogging.logError("Motor moved too fast!"); else if(output == FinalState.UNSAFE) - ErrorLogging.logError("DEBUG: Motor movement slowed down, but not fully."); + ErrorLogging.logError("DEBUG: Motor only slowed down partially."); else if(output == FinalState.SAFE) - ErrorLogging.logError("DEBUG: Motor movement successfully slowed fully."); + { + if(limitSense.isOn()) ErrorLogging.logError("DEBUG: Motor slowed down successfully."); + else ErrorLogging.logError("DEBUG: Motor did not reach limit switch successfully."); + } pwm.on(DUTY_CYCLE, FREQUENCY); return output; } diff --git a/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java b/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java index b861e43..80ce246 100644 --- a/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java +++ b/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java @@ -84,6 +84,7 @@ public class OpenCVFacade { String cameraName = cameraFile.getName(). substring(CAMERA_FILE_PREFIX.length()); + ErrorLogging.logError("DEBUG: Camera name: " + cameraName); newCamera(cameraName, cameraFile.getAbsolutePath()); } } -- 2.47.2 From dae70b168f758333c00b9e5fac03553d63b2b87a Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Wed, 8 Mar 2023 08:59:41 -0500 Subject: [PATCH 09/12] Add gotolimit check to stop motor preemptively --- src/main/java/org/baxter/disco/ocr/MovementFacade.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/baxter/disco/ocr/MovementFacade.java b/src/main/java/org/baxter/disco/ocr/MovementFacade.java index 8d112cf..b889c47 100644 --- a/src/main/java/org/baxter/disco/ocr/MovementFacade.java +++ b/src/main/java/org/baxter/disco/ocr/MovementFacade.java @@ -438,7 +438,6 @@ public class MovementFacade ErrorLogging.logError("DEBUG: Motor calibrate off."); if(upperLimit.isHigh()) { - ErrorLogging.logError("DEBUG: Upper limit is high = " + upperLimit.isHigh()); ErrorLogging.logError("DEBUG: Motor failed to move! Returning " + (i - iterate)); return i-iterate; } @@ -527,7 +526,12 @@ public class MovementFacade for(int i = 0; i < (POLL_COUNT);i++) { try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); } - if(i >= VEL_STEP_1 && i < VEL_STEP_2) + if(limitSense.isOn()) + { + ErrorLogging.logError("DEBUG: Motor at lower limit switch early! Pre-emptively exiting loop on poll " + i); + break; + } + else if(i >= VEL_STEP_1 && i < VEL_STEP_2) { if(output != FinalState.UNSAFE) { -- 2.47.2 From 1ef2fecb570623de9683f661eadf0ec10b913158 Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Wed, 8 Mar 2023 14:32:59 -0500 Subject: [PATCH 10/12] Finalise automated motor control --- src/main/java/org/baxter/disco/ocr/Cli.java | 125 ++++++----- .../org/baxter/disco/ocr/MovementFacade.java | 207 +++++++++++------- 2 files changed, 190 insertions(+), 142 deletions(-) diff --git a/src/main/java/org/baxter/disco/ocr/Cli.java b/src/main/java/org/baxter/disco/ocr/Cli.java index a1b869d..bbce347 100644 --- a/src/main/java/org/baxter/disco/ocr/Cli.java +++ b/src/main/java/org/baxter/disco/ocr/Cli.java @@ -81,75 +81,80 @@ public class Cli ErrorLogging.logError("Version: " + version); ErrorLogging.logError("========================"); try{ - inputScanner = new Scanner(System.in); + //inputScanner = new Scanner(System.in); - ConfigFacade.init(); + //ConfigFacade.init(); - int userInput = 0; + //int userInput = 0; ErrorLogging.logError("Calibrating motor movement. This may take several minutes..."); - MovementFacade.calibrate(); + ErrorLogging.logError("The piston will fire momentarily when the motor calibration is complete."); + MovementFacade.pressButton(); - do - { - printMainMenu(); - userInput = (int)inputFiltering(inputScanner.nextLine()); + ErrorLogging.logError("Testing config."); + MovementFacade.goDown(); + MovementFacade.goUp(); - switch (userInput) - { - case 1: - println("Setting up cameras..."); - println("This may take a moment..."); - configureCameras(); - camerasConfigured = true; - break; - case 2: - setDUTSerials(); - break; - case 3: - setIterationCount(); - break; - case 4: - setActiveCameras(); - break; - case 5: - if(!camerasConfigured) - { - prompt("You have not configured the cameras yet! Are you sure you would like to continue? (y/N): "); - String input = inputScanner.nextLine().toLowerCase().trim(); - if( input.isBlank() || input.charAt(0) != 'y' ) break; - else - ErrorLogging.logError("DEBUG: Potential for error: Un-initialised cameras."); - } + //do + //{ + // printMainMenu(); + // userInput = (int)inputFiltering(inputScanner.nextLine()); - serialsSet = true; - for(String cameraName : OpenCVFacade.getCameraNames()) - { - if(ConfigFacade.getValue(cameraName,ConfigProperties.ACTIVE) != 0 && - ConfigFacade.getSerial(cameraName) == null ) - serialsSet = false; - } - if(!serialsSet) - { - prompt("You have not set the serial numbers for your DUTs yet! Are you sure you would like to continue? (y/N): "); - String input = inputScanner.nextLine().toLowerCase().trim(); - if( input.isBlank() || input.charAt(0) != 'y' ) break; - else - ErrorLogging.logError("DEBUG: Potential for error: Un-initialised DUT Serial numbers."); - } + // switch (userInput) + // { + // case 1: + // println("Setting up cameras..."); + // println("This may take a moment..."); + // configureCameras(); + // camerasConfigured = true; + // break; + // case 2: + // setDUTSerials(); + // break; + // case 3: + // setIterationCount(); + // break; + // case 4: + // setActiveCameras(); + // break; + // case 5: + // if(!camerasConfigured) + // { + // prompt("You have not configured the cameras yet! Are you sure you would like to continue? (y/N): "); + // String input = inputScanner.nextLine().toLowerCase().trim(); + // if( input.isBlank() || input.charAt(0) != 'y' ) break; + // else + // ErrorLogging.logError("DEBUG: Potential for error: Un-initialised cameras."); + // } - runTests(); - break; - case 6: - printHelp(); - break; - case 8: - break; - default: - //Input handling already done by inputFiltering() - } + // serialsSet = true; + // for(String cameraName : OpenCVFacade.getCameraNames()) + // { + // if(ConfigFacade.getValue(cameraName,ConfigProperties.ACTIVE) != 0 && + // ConfigFacade.getSerial(cameraName) == null ) + // serialsSet = false; + // } + // if(!serialsSet) + // { + // prompt("You have not set the serial numbers for your DUTs yet! Are you sure you would like to continue? (y/N): "); + // String input = inputScanner.nextLine().toLowerCase().trim(); + // if( input.isBlank() || input.charAt(0) != 'y' ) break; + // else + // ErrorLogging.logError("DEBUG: Potential for error: Un-initialised DUT Serial numbers."); + // } - } while (userInput != mainMenuOptionCount); + // runTests(); + // break; + // case 6: + // printHelp(); + // break; + // case 8: + // break; + // default: + // //Input handling already done by inputFiltering() + // } + + //} while (userInput != mainMenuOptionCount); } //If anything ever goes wrong, catch the error and exit diff --git a/src/main/java/org/baxter/disco/ocr/MovementFacade.java b/src/main/java/org/baxter/disco/ocr/MovementFacade.java index b889c47..6d9ac7f 100644 --- a/src/main/java/org/baxter/disco/ocr/MovementFacade.java +++ b/src/main/java/org/baxter/disco/ocr/MovementFacade.java @@ -35,19 +35,6 @@ public class MovementFacade */ private static Thread runSwitchThread; - /** - * Conversion factor from cm/s to PWM frequency. - * - * PWM to linear speed conversion: - * Motor controller should be set to 6400 pulses / revolution - * (See motor controller documentation) - * Fixture corkscrew has a lead of 3 revolutions / cm - * (Lead = thread pitch * # of thread starts) - * - * Frequency (Hz) = Speed (cm/s) * (pulses/rev) * (lead) - */ - private static final double PWM_FREQ_CONVERT = 19200; - /** * Max allowed speed by current fixture design. * Motor appears to start acting erratically over 192kHz. @@ -65,26 +52,21 @@ public class MovementFacade private static final int MIN_FREQUENCY = 10000; /** - * Distance in cm the fixture needs to travel. - * - * Distance between limit switches: ~80cm. - * Thickness of fixture arm: ~50cm. - * *** ABOVE MATH IS WRONG *** - * I don't know what I did wrong, but this value doesn't work - * properly if straight calculated. - * I will look into this more at some point. + * Fraction of the total travel time at speed to start slowing down. */ - private static final int TRAVEL_DIST = 12; + private static final double SLOW_POLL_FACTOR = 3.0 / 4.0; /** - * What percentage of the travel to slow down the motor. + * Amount to slow down the speed by. */ - private static final double STEP_1 = 2.0/3.0; + private static final int SPEED_DOWN_FACTOR = 2; /** - * What percentage of the travel to slow down the motor farther. + * Amount of distance to travel. + * Measured in... seemingly arbitrary units? Not sure on the math here. + * Set in {@link #findDistance()} */ - private static final double STEP_2 = 5.0/6.0; + private static double TRAVEL_DIST; /** * Frequency fed to the PWM pin, which the motor controller converts into movement speed. @@ -231,7 +213,6 @@ public class MovementFacade } }, "Run switch monitor."); runSwitchThread.start(); - //ErrorLogging.logError("DEBUG: Lock thread started!"); //Initialise Pi4J pi4j = Pi4J.newAutoContext(); @@ -249,6 +230,9 @@ public class MovementFacade //Initialise PWM object. pwm = pwmBuilder("pwm","PWM Pin",PWM_PIN_ADDR); pwm.on(DUTY_CYCLE, FREQUENCY); + + //Find Distance and max speeds + calibrate(); } /** @@ -351,7 +335,7 @@ public class MovementFacade public static int resetArm() { ErrorLogging.logError("DEBUG: --------------------------------------"); - int counter = 0; + int counter; ErrorLogging.logError("DEBUG: Setting minimum frequency of PWM..."); pwm.on(DUTY_CYCLE, MIN_FREQUENCY); if(upperLimit.isHigh()) @@ -372,24 +356,29 @@ public class MovementFacade motorEnable.on(); ErrorLogging.logError("DEBUG: Is the upper limit switch reached? " + upperLimit.isHigh()); - while(!upperLimit.isHigh()) + for(counter = 0; counter < Integer.MAX_VALUE; counter++) { - try{ Thread.sleep(100); } - catch (Exception e) { ErrorLogging.logError(e); } - counter++; + try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); } + if(upperLimit.isOn()) + { + try{ Thread.sleep(1); } catch(Exception e){ErrorLogging.logError(e); } + if(upperLimit.isOn()) break; + } } motorEnable.off(); - ErrorLogging.logError("DEBUG: Motor returned after " + counter + " polls."); + ErrorLogging.logError("Motor returned after " + counter + " polls."); ErrorLogging.logError("DEBUG: --------------------------------------"); return counter; } /** - * Used to set the motor's max speed. + * Used to programmatically determine the motor's max speed. */ - public static void calibrate() + private static void calibrate() { - ErrorLogging.logError("Initial Calibration reset."); + ErrorLogging.logError("Determining distance to limit switches..."); + findDistance(); + ErrorLogging.logError("Resetting arm to set speed."); resetArm(); ErrorLogging.logError("Coarse calibrating..."); FREQUENCY = calib(MIN_FREQUENCY, MAX_FREQUENCY, 10000); @@ -401,10 +390,64 @@ public class MovementFacade } /** - * Find the max speed of the fixure between two points. + * Used to programmatically find the distance between the upper and lower limit switches. + */ + private static void findDistance() + { + resetArm(); + int downTravelCounter = 0; + int upTravelCounter = 0; + pwm.on(DUTY_CYCLE, MIN_FREQUENCY); + motorDirection.low(); + motorEnable.on(); + for(downTravelCounter = 0; downTravelCounter < Integer.MAX_VALUE; downTravelCounter++) + { + try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); } + if(lowerLimit.isOn()) + { + try{ Thread.sleep(1); } catch(Exception e){ErrorLogging.logError(e); } + if(lowerLimit.isOn()) break; + } + } + motorEnable.off(); + if(lowerLimit.isOff()) ErrorLogging.logError("False positive on findDistance down!"); + + int downTravelDist = downTravelCounter * MIN_FREQUENCY; + ErrorLogging.logError("DEBUG: Down travel distance found to be: " + downTravelDist); + ErrorLogging.logError("Down travel count: " + downTravelCounter); + + motorDirection.high(); + motorEnable.on(); + for(upTravelCounter = 0; upTravelCounter < Integer.MAX_VALUE; upTravelCounter++) + { + try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); } + if(upperLimit.isOn()) + { + try{ Thread.sleep(1); } catch(Exception e){ErrorLogging.logError(e); } + if(upperLimit.isOn()) break; + } + } + motorEnable.off(); + if(upperLimit.isOff()) ErrorLogging.logError("False positive on findDistance up!"); + + int upTravelDist = upTravelCounter * MIN_FREQUENCY; + ErrorLogging.logError("DEBUG: Up travel distance found to be: " + upTravelDist); + ErrorLogging.logError("Up travel count: " + downTravelCounter); + + if(Math.abs(upTravelCounter - downTravelCounter) > 3) + { + ErrorLogging.logError("DEBUG: Values differ too far to be error. Setting to lower value."); + } + int travelCounter = Math.min(upTravelCounter, downTravelCounter); + TRAVEL_DIST = travelCounter * MIN_FREQUENCY; + } + + + /** + * Find the max frequency to feed to the motor. * - * @param start Lowest speed to check - * @param max Highest speed to check + * @param start Lowest frequency to check + * @param max Highest frequency to check * @param iterate How much to iterate by * * @return The largest safe value between start and max. @@ -424,10 +467,10 @@ public class MovementFacade ErrorLogging.logError("DEBUG: Motor Frequency: " + FREQUENCY); ErrorLogging.logError("DEBUG: Motor calibrate on."); motorEnable.on(); - for(int j = 0; j < 20; j++) + int TWO_SECONDS = 2 * TIME_CONVERSION; + for(int j = 0; j < TWO_SECONDS; j++) { - try{ Thread.sleep(100); } - catch (Exception e){ ErrorLogging.logError(e); } + try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); } if(lowerLimit.isHigh()) { ErrorLogging.logError("DEBUG: Breaking loop early!"); @@ -445,7 +488,7 @@ public class MovementFacade { ErrorLogging.logError("DEBUG: Motor moved at speed " + i + ". Checking for errors."); - if(resetArm() < 10 && i > 3.0) + if(resetArm() < 10) { ErrorLogging.logError("DEBUG: Motor failed to move! Returning " + (i - iterate)); return i - iterate; @@ -508,59 +551,59 @@ public class MovementFacade if(limitSense.isHigh()) return FinalState.SAFE; pwm.on(DUTY_CYCLE, FREQUENCY); - int TRAVEL_TIME = (int)(TRAVEL_DIST / (FREQUENCY / PWM_FREQ_CONVERT)); - int POLL_COUNT = TRAVEL_TIME * TIME_CONVERSION; - int VEL_STEP_1 = (int)(STEP_1 * POLL_COUNT); - int VEL_STEP_2 = (int)(STEP_2 * POLL_COUNT); - - ErrorLogging.logError("DEBUG: ================================="); - ErrorLogging.logError("DEBUG: Total Poll count: " + POLL_COUNT); - ErrorLogging.logError("DEBUG: Transition 1: " + VEL_STEP_1); - ErrorLogging.logError("DEBUG: Transition 2: " + VEL_STEP_2); - ErrorLogging.logError("DEBUG: Travel time: " + TRAVEL_TIME); - ErrorLogging.logError("DEBUG: Travel speed: " + (FREQUENCY/PWM_FREQ_CONVERT)); - ErrorLogging.logError("DEBUG: STEP_1: " + STEP_1); - ErrorLogging.logError("DEBUG: STEP_2: " + STEP_2); - ErrorLogging.logError("DEBUG: ================================="); + int travelTime = (int)(TRAVEL_DIST / FREQUENCY); + int totalPollCount = travelTime * TIME_CONVERSION; + int highSpeedPolls = (int)(totalPollCount * SLOW_POLL_FACTOR); + int notHighSpeedPolls = totalPollCount - highSpeedPolls; + int medSpeedPolls = (int)(notHighSpeedPolls * SLOW_POLL_FACTOR); + int lowSpeedPolls = notHighSpeedPolls - medSpeedPolls; motorEnable.on(); - for(int i = 0; i < (POLL_COUNT);i++) + for(int i = 0; i < highSpeedPolls; i++) { try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); } if(limitSense.isOn()) { - ErrorLogging.logError("DEBUG: Motor at lower limit switch early! Pre-emptively exiting loop on poll " + i); + ErrorLogging.logError("DEBUG: Motor moved too fast! Stopping motor early."); + motorEnable.off(); + output = FinalState.FAILED; break; } - else if(i >= VEL_STEP_1 && i < VEL_STEP_2) + } + + if(motorEnable.isOn()) + { + output = FinalState.UNSAFE; + pwm.on(DUTY_CYCLE, (FREQUENCY / SPEED_DOWN_FACTOR)); + for(int i = 0; i < medSpeedPolls; i++) { - if(output != FinalState.UNSAFE) + try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); } + if(limitSense.isOn()) { - output = FinalState.UNSAFE; - ErrorLogging.logError("DEBUG: Slowing down."); - pwm.on(DUTY_CYCLE, (int)(FREQUENCY / 1.25)); - } - } - else if(i >= VEL_STEP_2) - { - if(output != FinalState.SAFE) - { - ErrorLogging.logError("DEBUG: Slowing down more."); - pwm.on(DUTY_CYCLE, FREQUENCY / 2); - output = FinalState.SAFE; + ErrorLogging.logError("DEBUG: Motor only partially slowed down! Stopping motor early."); + motorEnable.off(); + break; } } } + + if(motorEnable.isOn()) + { + output = FinalState.SAFE; + pwm.on(DUTY_CYCLE, (FREQUENCY / SPEED_DOWN_FACTOR)); + for(int i = 0; i < lowSpeedPolls; i++) + { + try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); } + if(limitSense.isOn()) + { + ErrorLogging.logError("DEBUG: Motor slowed down completely, but hit limit switch early."); + motorEnable.off(); + break; + } + } + } + motorEnable.off(); - if(output == FinalState.FAILED) - ErrorLogging.logError("Motor moved too fast!"); - else if(output == FinalState.UNSAFE) - ErrorLogging.logError("DEBUG: Motor only slowed down partially."); - else if(output == FinalState.SAFE) - { - if(limitSense.isOn()) ErrorLogging.logError("DEBUG: Motor slowed down successfully."); - else ErrorLogging.logError("DEBUG: Motor did not reach limit switch successfully."); - } pwm.on(DUTY_CYCLE, FREQUENCY); return output; } -- 2.47.2 From f22d9f7b8dadd295dbfcb54a404ffdda064498aa Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Wed, 8 Mar 2023 15:22:43 -0500 Subject: [PATCH 11/12] Hide debug info, uncomment main menu --- src/main/java/org/baxter/disco/ocr/Cli.java | 118 +++++++++--------- .../org/baxter/disco/ocr/MovementFacade.java | 41 +++--- 2 files changed, 85 insertions(+), 74 deletions(-) diff --git a/src/main/java/org/baxter/disco/ocr/Cli.java b/src/main/java/org/baxter/disco/ocr/Cli.java index bbce347..4be29fe 100644 --- a/src/main/java/org/baxter/disco/ocr/Cli.java +++ b/src/main/java/org/baxter/disco/ocr/Cli.java @@ -81,11 +81,11 @@ public class Cli ErrorLogging.logError("Version: " + version); ErrorLogging.logError("========================"); try{ - //inputScanner = new Scanner(System.in); + inputScanner = new Scanner(System.in); - //ConfigFacade.init(); + ConfigFacade.init(); - //int userInput = 0; + int userInput = 0; ErrorLogging.logError("Calibrating motor movement. This may take several minutes..."); ErrorLogging.logError("The piston will fire momentarily when the motor calibration is complete."); @@ -95,66 +95,66 @@ public class Cli MovementFacade.goDown(); MovementFacade.goUp(); - //do - //{ - // printMainMenu(); - // userInput = (int)inputFiltering(inputScanner.nextLine()); + do + { + printMainMenu(); + userInput = (int)inputFiltering(inputScanner.nextLine()); - // switch (userInput) - // { - // case 1: - // println("Setting up cameras..."); - // println("This may take a moment..."); - // configureCameras(); - // camerasConfigured = true; - // break; - // case 2: - // setDUTSerials(); - // break; - // case 3: - // setIterationCount(); - // break; - // case 4: - // setActiveCameras(); - // break; - // case 5: - // if(!camerasConfigured) - // { - // prompt("You have not configured the cameras yet! Are you sure you would like to continue? (y/N): "); - // String input = inputScanner.nextLine().toLowerCase().trim(); - // if( input.isBlank() || input.charAt(0) != 'y' ) break; - // else - // ErrorLogging.logError("DEBUG: Potential for error: Un-initialised cameras."); - // } + switch (userInput) + { + case 1: + println("Setting up cameras..."); + println("This may take a moment..."); + configureCameras(); + camerasConfigured = true; + break; + case 2: + setDUTSerials(); + break; + case 3: + setIterationCount(); + break; + case 4: + setActiveCameras(); + break; + case 5: + if(!camerasConfigured) + { + prompt("You have not configured the cameras yet! Are you sure you would like to continue? (y/N): "); + String input = inputScanner.nextLine().toLowerCase().trim(); + if( input.isBlank() || input.charAt(0) != 'y' ) break; + else + ErrorLogging.logError("DEBUG: Potential for error: Un-initialised cameras."); + } - // serialsSet = true; - // for(String cameraName : OpenCVFacade.getCameraNames()) - // { - // if(ConfigFacade.getValue(cameraName,ConfigProperties.ACTIVE) != 0 && - // ConfigFacade.getSerial(cameraName) == null ) - // serialsSet = false; - // } - // if(!serialsSet) - // { - // prompt("You have not set the serial numbers for your DUTs yet! Are you sure you would like to continue? (y/N): "); - // String input = inputScanner.nextLine().toLowerCase().trim(); - // if( input.isBlank() || input.charAt(0) != 'y' ) break; - // else - // ErrorLogging.logError("DEBUG: Potential for error: Un-initialised DUT Serial numbers."); - // } + serialsSet = true; + for(String cameraName : OpenCVFacade.getCameraNames()) + { + if(ConfigFacade.getValue(cameraName,ConfigProperties.ACTIVE) != 0 && + ConfigFacade.getSerial(cameraName) == null ) + serialsSet = false; + } + if(!serialsSet) + { + prompt("You have not set the serial numbers for your DUTs yet! Are you sure you would like to continue? (y/N): "); + String input = inputScanner.nextLine().toLowerCase().trim(); + if( input.isBlank() || input.charAt(0) != 'y' ) break; + else + ErrorLogging.logError("DEBUG: Potential for error: Un-initialised DUT Serial numbers."); + } - // runTests(); - // break; - // case 6: - // printHelp(); - // break; - // case 8: - // break; - // default: - // //Input handling already done by inputFiltering() - // } + runTests(); + break; + case 6: + printHelp(); + break; + case 8: + break; + default: + //Input handling already done by inputFiltering() + } - //} while (userInput != mainMenuOptionCount); + } while (userInput != mainMenuOptionCount); } //If anything ever goes wrong, catch the error and exit diff --git a/src/main/java/org/baxter/disco/ocr/MovementFacade.java b/src/main/java/org/baxter/disco/ocr/MovementFacade.java index 6d9ac7f..d097f00 100644 --- a/src/main/java/org/baxter/disco/ocr/MovementFacade.java +++ b/src/main/java/org/baxter/disco/ocr/MovementFacade.java @@ -44,7 +44,7 @@ public class MovementFacade /** * Amount of buffer between the found absolute speed, and used speed. */ - private static final int SPEED_BUFFER = 4000; + private static final int SPEED_BUFFER = 5000; /** * Minimum allowed speed of the fixture arm; also used for reset travels. @@ -366,7 +366,7 @@ public class MovementFacade } } motorEnable.off(); - ErrorLogging.logError("Motor returned after " + counter + " polls."); + ErrorLogging.logError("DEBUG: Motor returned after " + counter + " polls."); ErrorLogging.logError("DEBUG: --------------------------------------"); return counter; } @@ -380,10 +380,10 @@ public class MovementFacade findDistance(); ErrorLogging.logError("Resetting arm to set speed."); resetArm(); - ErrorLogging.logError("Coarse calibrating..."); + ErrorLogging.logError("Calibrating..."); FREQUENCY = calib(MIN_FREQUENCY, MAX_FREQUENCY, 10000); - ErrorLogging.logError("Fine calibrating..."); - FREQUENCY = calib(FREQUENCY,(FREQUENCY+10000),1000); + //ErrorLogging.logError("Fine calibrating..."); + //FREQUENCY = calib(FREQUENCY,(FREQUENCY+10000),1000); ErrorLogging.logError("Calibration complete!"); ErrorLogging.logError("DEBUG: Speed set to " + (FREQUENCY - SPEED_BUFFER)); setFrequency(FREQUENCY - SPEED_BUFFER); @@ -410,11 +410,11 @@ public class MovementFacade } } motorEnable.off(); - if(lowerLimit.isOff()) ErrorLogging.logError("False positive on findDistance down!"); + if(lowerLimit.isOff()) ErrorLogging.logError("DEBUG: False positive on findDistance down!"); int downTravelDist = downTravelCounter * MIN_FREQUENCY; ErrorLogging.logError("DEBUG: Down travel distance found to be: " + downTravelDist); - ErrorLogging.logError("Down travel count: " + downTravelCounter); + ErrorLogging.logError("DEBUG: Down travel count: " + downTravelCounter); motorDirection.high(); motorEnable.on(); @@ -428,11 +428,11 @@ public class MovementFacade } } motorEnable.off(); - if(upperLimit.isOff()) ErrorLogging.logError("False positive on findDistance up!"); + if(upperLimit.isOff()) ErrorLogging.logError("DEBUG: False positive on findDistance up!"); int upTravelDist = upTravelCounter * MIN_FREQUENCY; ErrorLogging.logError("DEBUG: Up travel distance found to be: " + upTravelDist); - ErrorLogging.logError("Up travel count: " + downTravelCounter); + ErrorLogging.logError("DEBUG: Up travel count: " + downTravelCounter); if(Math.abs(upTravelCounter - downTravelCounter) > 3) { @@ -526,7 +526,6 @@ public class MovementFacade * Internal function to send the fixture to a given limit switch. * * Detects if the limit switch is active before activating motor. - * Motor movement is written to slow down at {@link #STEP_1} and {@link #STEP_2} * * @param moveUp Whether to send the fixture up or down. (True = up, False = down) * @return true if movement was successful; otherwise false @@ -551,20 +550,28 @@ public class MovementFacade if(limitSense.isHigh()) return FinalState.SAFE; pwm.on(DUTY_CYCLE, FREQUENCY); - int travelTime = (int)(TRAVEL_DIST / FREQUENCY); - int totalPollCount = travelTime * TIME_CONVERSION; + int totalPollCount = (int)(TRAVEL_DIST / FREQUENCY); int highSpeedPolls = (int)(totalPollCount * SLOW_POLL_FACTOR); int notHighSpeedPolls = totalPollCount - highSpeedPolls; int medSpeedPolls = (int)(notHighSpeedPolls * SLOW_POLL_FACTOR); int lowSpeedPolls = notHighSpeedPolls - medSpeedPolls; + + ErrorLogging.logError("DEBUG: ============================="); + ErrorLogging.logError("DEBUG: Travel time: " + totalPollCount); + ErrorLogging.logError("DEBUG: High speed poll count: " + highSpeedPolls); + ErrorLogging.logError("DEBUG: Medium speed poll count: " + medSpeedPolls); + ErrorLogging.logError("DEBUG: Low speed poll count: " + lowSpeedPolls); + ErrorLogging.logError("DEBUG: ============================="); motorEnable.on(); for(int i = 0; i < highSpeedPolls; i++) { try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); } if(limitSense.isOn()) { - ErrorLogging.logError("DEBUG: Motor moved too fast! Stopping motor early."); motorEnable.off(); + ErrorLogging.logError("DEBUG: Motor moved too fast! Stopping motor early."); + ErrorLogging.logError("DEBUG: Breaking high-speed loop and turning off motor!"); + ErrorLogging.logError("DEBUG: Iteration count: " + i); output = FinalState.FAILED; break; } @@ -579,8 +586,10 @@ public class MovementFacade try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); } if(limitSense.isOn()) { - ErrorLogging.logError("DEBUG: Motor only partially slowed down! Stopping motor early."); motorEnable.off(); + ErrorLogging.logError("DEBUG: Motor only partially slowed down! Stopping motor early."); + ErrorLogging.logError("DEBUG: Breaking medium-speed loop and turning off motor!"); + ErrorLogging.logError("DEBUG: Iteration count: " + i); break; } } @@ -595,8 +604,10 @@ public class MovementFacade try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); } if(limitSense.isOn()) { - ErrorLogging.logError("DEBUG: Motor slowed down completely, but hit limit switch early."); motorEnable.off(); + ErrorLogging.logError("DEBUG: Motor slowed down completely, but hit limit switch early."); + ErrorLogging.logError("DEBUG: Breaking low-speed loop and turning off motor!"); + ErrorLogging.logError("DEBUG: Iteration count: " + i); break; } } -- 2.47.2 From df85060d87cc11fbe743ba2b4dcc6a83d27742b6 Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Wed, 8 Mar 2023 15:33:01 -0500 Subject: [PATCH 12/12] Minor fixes to mvmt code, remove test from CLI --- src/main/java/org/baxter/disco/ocr/Cli.java | 4 ---- src/main/java/org/baxter/disco/ocr/MovementFacade.java | 5 ++++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/baxter/disco/ocr/Cli.java b/src/main/java/org/baxter/disco/ocr/Cli.java index 4be29fe..b4ae445 100644 --- a/src/main/java/org/baxter/disco/ocr/Cli.java +++ b/src/main/java/org/baxter/disco/ocr/Cli.java @@ -91,10 +91,6 @@ public class Cli ErrorLogging.logError("The piston will fire momentarily when the motor calibration is complete."); MovementFacade.pressButton(); - ErrorLogging.logError("Testing config."); - MovementFacade.goDown(); - MovementFacade.goUp(); - do { printMainMenu(); diff --git a/src/main/java/org/baxter/disco/ocr/MovementFacade.java b/src/main/java/org/baxter/disco/ocr/MovementFacade.java index d097f00..2d2e141 100644 --- a/src/main/java/org/baxter/disco/ocr/MovementFacade.java +++ b/src/main/java/org/baxter/disco/ocr/MovementFacade.java @@ -556,6 +556,9 @@ public class MovementFacade int medSpeedPolls = (int)(notHighSpeedPolls * SLOW_POLL_FACTOR); int lowSpeedPolls = notHighSpeedPolls - medSpeedPolls; + medSpeedPolls *= SPEED_DOWN_FACTOR; + lowSpeedPolls *= 2 * SPEED_DOWN_FACTOR; + ErrorLogging.logError("DEBUG: ============================="); ErrorLogging.logError("DEBUG: Travel time: " + totalPollCount); ErrorLogging.logError("DEBUG: High speed poll count: " + highSpeedPolls); @@ -598,7 +601,7 @@ public class MovementFacade if(motorEnable.isOn()) { output = FinalState.SAFE; - pwm.on(DUTY_CYCLE, (FREQUENCY / SPEED_DOWN_FACTOR)); + pwm.on(DUTY_CYCLE, (FREQUENCY / (2 * SPEED_DOWN_FACTOR))); for(int i = 0; i < lowSpeedPolls; i++) { try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); } -- 2.47.2