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()); } }