v. 4.3.5 Update #13
12 changed files with 480 additions and 470 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<groupId>org.baxter.disco</groupId>
|
||||
<artifactId>ocr</artifactId>
|
||||
<name>Disco OCR Accuracy Over Life Testing</name>
|
||||
<version>4.3.4</version>
|
||||
<version>4.3.5</version>
|
||||
<description>Testing Discos for long-term accuracy, using automated optical character recognition.</description>
|
||||
<organization>
|
||||
<name>Baxter International</name>
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -3,7 +3,7 @@
|
|||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.baxter.disco</groupId>
|
||||
<artifactId>ocr</artifactId>
|
||||
<version>4.3.4</version>
|
||||
<version>4.3.5</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>Disco OCR Accuracy Over Life Testing</name>
|
||||
<description>Testing Discos for long-term accuracy, using automated optical character recognition.</description>
|
||||
|
|
|
@ -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.6.jar 2>/dev/null
|
||||
|
|
|
@ -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.4";
|
||||
private static final String version = "4.3.5";
|
||||
|
||||
/**
|
||||
* Currently saved iteration count.
|
||||
|
@ -53,27 +53,17 @@ public class Cli
|
|||
/**
|
||||
* Number of options currently available in the main menu.
|
||||
*/
|
||||
private static final int mainMenuOptionCount = 8;
|
||||
|
||||
/**
|
||||
* Number of options currently available in the movement sub-menu.
|
||||
*/
|
||||
private static final int movementMenuOptionCount = 4;
|
||||
private static final int mainMenuOptionCount = 7;
|
||||
|
||||
/**
|
||||
* 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,118 +81,76 @@ 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
|
||||
ErrorLogging.logError("Calibrating motor movement. This may take several minutes...");
|
||||
ErrorLogging.logError("The piston will fire momentarily when the motor calibration is complete.");
|
||||
MovementFacade.pressButton();
|
||||
|
||||
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();
|
||||
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
|
||||
case 2:
|
||||
setDUTSerials();
|
||||
break;
|
||||
case 4:
|
||||
//Change the number of iterations to run the tests
|
||||
case 3:
|
||||
setIterationCount();
|
||||
break;
|
||||
case 5:
|
||||
//Set cameras to use in testing
|
||||
case 4:
|
||||
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
|
||||
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();
|
||||
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
|
||||
case 6:
|
||||
printHelp();
|
||||
break;
|
||||
case 8:
|
||||
//Leave the menu
|
||||
break;
|
||||
default:
|
||||
//Input handling already done by inputFiltering()
|
||||
}
|
||||
|
||||
} while (userInput != mainMenuOptionCount);
|
||||
} while (userInput != mainMenuOptionCount);
|
||||
|
||||
}
|
||||
//If anything ever goes wrong, catch the error and exit
|
||||
|
@ -253,7 +201,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("----------------------------------------");
|
||||
|
@ -301,37 +248,16 @@ 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 Duty Cycle: " + fixture.getDutyCycle());
|
||||
println("Current Frequency: " + fixture.getFrequency());
|
||||
println("Current Motor Time-out: " + fixture.getTimeout());
|
||||
println("------------------------------------");
|
||||
println("1. Change Duty Cycle");
|
||||
println("2. Change Frequency");
|
||||
println("3. Change Motor Time-out");
|
||||
println("4. Exit");
|
||||
println("====================================");
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-defined method for printing all available cameras in a menu
|
||||
*/
|
||||
|
@ -408,7 +334,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");
|
||||
|
@ -417,62 +343,6 @@ public class Cli
|
|||
println("====================================");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function for testing movement, and modifying hardware values
|
||||
*/
|
||||
private static void testMovement()
|
||||
{
|
||||
int userInput = -1;
|
||||
//Loop to allow multiple changes to device GPIO settings
|
||||
do
|
||||
{
|
||||
println("Testing movement...");
|
||||
fixture.testMotions();
|
||||
printMovementMenu();
|
||||
userInput = (int)inputFiltering(inputScanner.nextLine());
|
||||
switch (userInput)
|
||||
{
|
||||
/*
|
||||
* Menu options:
|
||||
* 1. Change Duty Cycle
|
||||
* 2. Change Frequency
|
||||
* 3. Change Motor Time-out
|
||||
* 4. 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: ");
|
||||
int newFrequency = (int)inputFiltering(inputScanner.nextLine());
|
||||
if (newFrequency != -1)
|
||||
{
|
||||
fixture.setFrequency(newFrequency);
|
||||
break;
|
||||
}
|
||||
case 3:
|
||||
prompt("Input the desired time-out (in seconds): ");
|
||||
double newTimeout = inputFiltering(inputScanner.nextLine());
|
||||
if (newTimeout != -1)
|
||||
{
|
||||
fixture.setTimeout(newTimeout);
|
||||
break;
|
||||
}
|
||||
case 4:
|
||||
break;
|
||||
default:
|
||||
ErrorLogging.logError("User Input Error!!! - Invalid input.");
|
||||
}
|
||||
}
|
||||
while(userInput != 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-function used to configure cameras.
|
||||
*/
|
||||
|
@ -481,7 +351,7 @@ public class Cli
|
|||
List<String> 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 +379,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 +577,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 +609,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 +648,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;
|
||||
}
|
||||
|
@ -798,8 +668,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!");
|
||||
|
@ -817,9 +685,11 @@ public class Cli
|
|||
*/
|
||||
private static void close()
|
||||
{
|
||||
ErrorLogging.logError("DEBUG: =================");
|
||||
ErrorLogging.logError("DEBUG: PROGRAM CLOSING.");
|
||||
ErrorLogging.logError("DEBUG: =================");
|
||||
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.");
|
||||
|
@ -867,13 +737,6 @@ public class Cli
|
|||
output = -1;
|
||||
}
|
||||
break;
|
||||
case MOVEMENT:
|
||||
if(output > movementMenuOptionCount)
|
||||
{
|
||||
invalidMovementMenuInput();
|
||||
output = -1;
|
||||
}
|
||||
break;
|
||||
case CAMERA:
|
||||
if(output > cameraMenuOptionCount)
|
||||
{
|
||||
|
@ -896,14 +759,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.
|
||||
*/
|
||||
|
@ -954,5 +809,5 @@ public class Cli
|
|||
/**
|
||||
* Enum of possible menus available
|
||||
*/
|
||||
private enum Menus { MAIN,MOVEMENT,CAMERA,OTHER; }
|
||||
private enum Menus { MAIN,CAMERA,OTHER; }
|
||||
}
|
||||
|
|
|
@ -76,6 +76,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 +141,7 @@ public class ConfigFacade
|
|||
//Autosave the config
|
||||
CONFIG_BUILDER.setAutoSave(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a given config value.
|
||||
* All values are stored as doubles.
|
||||
|
@ -169,7 +171,6 @@ public class ConfigFacade
|
|||
Map<ConfigProperties,Double> 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;
|
||||
|
@ -277,6 +278,7 @@ public class ConfigFacade
|
|||
//**********************************************
|
||||
//SAVE AND LOAD SETTINGS
|
||||
//**********************************************
|
||||
//
|
||||
|
||||
/**
|
||||
* Save current config to a user-defined file location.
|
||||
|
@ -433,22 +435,10 @@ public class ConfigFacade
|
|||
for(String sectionName : configSections)
|
||||
{
|
||||
Map<ConfigProperties,Double> 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())
|
||||
|
@ -482,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<ConfigProperties,Double> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);}
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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,62 +20,64 @@ import com.pi4j.io.pwm.PwmType;
|
|||
* Currently missing Run switch compatibility.
|
||||
*
|
||||
* @author Blizzard Finnegan
|
||||
* @version 2.3.0, 27 Feb. 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;
|
||||
|
||||
//Externally Available Variables
|
||||
/**
|
||||
* PWM Frequency
|
||||
* Max allowed speed by current fixture design.
|
||||
* Motor appears to start acting erratically over 192kHz.
|
||||
*/
|
||||
private static int FREQUENCY = 75000;
|
||||
private static final int MAX_FREQUENCY = 192000;
|
||||
|
||||
/**
|
||||
* PWM Duty Cycle
|
||||
* Amount of buffer between the found absolute speed, and used speed.
|
||||
*/
|
||||
private static int DUTY_CYCLE = 50;
|
||||
private static final int SPEED_BUFFER = 5000;
|
||||
|
||||
/**
|
||||
* Number of seconds to wait before timing out a fixture movement.
|
||||
* Minimum allowed speed of the fixture arm; also used for reset travels.
|
||||
*/
|
||||
private static double TIME_OUT = 2.5;
|
||||
private static final int MIN_FREQUENCY = 10000;
|
||||
|
||||
/**
|
||||
* Fraction of the total travel time at speed to start slowing down.
|
||||
*/
|
||||
private static final double SLOW_POLL_FACTOR = 3.0 / 4.0;
|
||||
|
||||
/**
|
||||
* Amount to slow down the speed by.
|
||||
*/
|
||||
private static final int SPEED_DOWN_FACTOR = 2;
|
||||
|
||||
/**
|
||||
* Amount of distance to travel.
|
||||
* Measured in... seemingly arbitrary units? Not sure on the math here.
|
||||
* Set in {@link #findDistance()}
|
||||
*/
|
||||
private static double TRAVEL_DIST;
|
||||
|
||||
/**
|
||||
* Frequency fed to the PWM pin, which the motor controller converts into movement speed.
|
||||
*/
|
||||
private static int FREQUENCY = MIN_FREQUENCY;
|
||||
|
||||
/**
|
||||
* PWM Duty Cycle.
|
||||
* Does not affect motor speed; necessary for PWM setup.
|
||||
*/
|
||||
private static final int DUTY_CYCLE = 50;
|
||||
|
||||
//PWM Addresses
|
||||
//All addresses are in BCM format.
|
||||
|
@ -121,13 +120,12 @@ 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.
|
||||
* 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
|
||||
|
||||
|
@ -192,6 +190,30 @@ public class MovementFacade
|
|||
|
||||
static
|
||||
{
|
||||
//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();
|
||||
|
||||
//Initialise Pi4J
|
||||
pi4j = Pi4J.newAutoContext();
|
||||
|
||||
|
@ -205,11 +227,12 @@ 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);
|
||||
|
||||
//Find Distance and max speeds
|
||||
calibrate();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -241,8 +264,8 @@ public class MovementFacade
|
|||
.name(name)
|
||||
.address(address)
|
||||
.pwmType(PwmType.HARDWARE)
|
||||
.provider("pigpio-pwm")
|
||||
.frequency(FREQUENCY)
|
||||
.provider("pigpio-pwm")
|
||||
.initial(1)
|
||||
//On program close, turn off PWM.
|
||||
.shutdown(0);
|
||||
|
@ -254,8 +277,8 @@ public class MovementFacade
|
|||
.name(name)
|
||||
.address(address)
|
||||
.pwmType(PwmType.SOFTWARE)
|
||||
.provider("pigpio-pwm")
|
||||
.frequency(FREQUENCY)
|
||||
.provider("pigpio-pwm")
|
||||
.initial(1)
|
||||
//On program close, turn off PWM.
|
||||
.shutdown(0);
|
||||
|
@ -307,136 +330,294 @@ 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.
|
||||
* Function used to locate the fixture's motor.
|
||||
*/
|
||||
public boolean setDutyCycle(int newDutyCycle)
|
||||
public static int resetArm()
|
||||
{
|
||||
boolean output = false;
|
||||
if(newDutyCycle < 0)
|
||||
ErrorLogging.logError("DEBUG: --------------------------------------");
|
||||
int counter;
|
||||
ErrorLogging.logError("DEBUG: Setting minimum frequency of PWM...");
|
||||
pwm.on(DUTY_CYCLE, MIN_FREQUENCY);
|
||||
if(upperLimit.isHigh())
|
||||
{
|
||||
ErrorLogging.logError("Movement error!!! - Invalid DutyCycle input.");
|
||||
ErrorLogging.logError("DEBUG: Motor at highest point! Lowering to reset.");
|
||||
motorDirection.low();
|
||||
ErrorLogging.logError("DEBUG: Motor offset on.");
|
||||
motorEnable.on();
|
||||
try{ Thread.sleep(500); }
|
||||
catch (Exception e){ ErrorLogging.logError(e); }
|
||||
motorEnable.off();
|
||||
ErrorLogging.logError("DEBUG: Motor offset off.");
|
||||
}
|
||||
else
|
||||
{
|
||||
DUTY_CYCLE = newDutyCycle;
|
||||
pwm.on(DUTY_CYCLE, FREQUENCY);
|
||||
output = true;
|
||||
ErrorLogging.logError("DEBUG: Moving motor to highest point.");
|
||||
motorDirection.high();
|
||||
|
||||
ErrorLogging.logError("DEBUG: Motor return on.");
|
||||
motorEnable.on();
|
||||
|
||||
ErrorLogging.logError("DEBUG: Is the upper limit switch reached? " + upperLimit.isHigh());
|
||||
for(counter = 0; counter < Integer.MAX_VALUE; 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;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
motorEnable.off();
|
||||
ErrorLogging.logError("DEBUG: Motor returned after " + counter + " polls.");
|
||||
ErrorLogging.logError("DEBUG: --------------------------------------");
|
||||
return counter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the fixture's PWM duty cycle.
|
||||
*
|
||||
* @return The current DutyCycle.
|
||||
* Used to programmatically determine the motor's max speed.
|
||||
*/
|
||||
public 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 boolean setTimeout(double newTimeout)
|
||||
private static void calibrate()
|
||||
{
|
||||
boolean output = false;
|
||||
if(newTimeout < 0)
|
||||
{
|
||||
ErrorLogging.logError("Movement error!!! - Invalid timeout input.");
|
||||
}
|
||||
else
|
||||
{
|
||||
TIME_OUT = newTimeout;
|
||||
POLL_COUNT = TIME_OUT * ( 1000 / POLL_WAIT );
|
||||
output = true;
|
||||
}
|
||||
return output;
|
||||
ErrorLogging.logError("Determining distance to limit switches...");
|
||||
findDistance();
|
||||
ErrorLogging.logError("Resetting arm to set speed.");
|
||||
resetArm();
|
||||
ErrorLogging.logError("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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the fixture's time to give up on a movement.
|
||||
*
|
||||
* @return The current timeout.
|
||||
* Used to programmatically find the distance between the upper and lower limit switches.
|
||||
*/
|
||||
public double getTimeout() { return TIME_OUT; }
|
||||
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("DEBUG: False positive on findDistance down!");
|
||||
|
||||
int downTravelDist = downTravelCounter * MIN_FREQUENCY;
|
||||
ErrorLogging.logError("DEBUG: Down travel distance found to be: " + downTravelDist);
|
||||
ErrorLogging.logError("DEBUG: 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("DEBUG: False positive on findDistance up!");
|
||||
|
||||
int upTravelDist = upTravelCounter * MIN_FREQUENCY;
|
||||
ErrorLogging.logError("DEBUG: Up travel distance found to be: " + upTravelDist);
|
||||
ErrorLogging.logError("DEBUG: 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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Setter for the fixture's PWM frequency.
|
||||
* Find the max frequency to feed to the motor.
|
||||
*
|
||||
* @param newFrequency The new frequency to be set by the user.
|
||||
* @param start Lowest frequency to check
|
||||
* @param max Highest frequency to check
|
||||
* @param iterate How much to iterate by
|
||||
*
|
||||
* @return True if the value was set successfully; otherwise false.
|
||||
* @return The largest safe value between start and max.
|
||||
*/
|
||||
public boolean setFrequency(int newFrequency)
|
||||
private static int calib(int start, int max, int iterate)
|
||||
{
|
||||
boolean output = false;
|
||||
if(newFrequency < 0)
|
||||
//start -= iterate;
|
||||
for(int i = start; i < max; i+=iterate)
|
||||
{
|
||||
ErrorLogging.logError("Movement error!!! - Invalid frequency input.");
|
||||
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();
|
||||
int TWO_SECONDS = 2 * TIME_CONVERSION;
|
||||
for(int j = 0; j < TWO_SECONDS; j++)
|
||||
{
|
||||
try{ Thread.sleep(POLL_WAIT); } 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: 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)
|
||||
{
|
||||
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 setFrequency(int newFrequency)
|
||||
{
|
||||
boolean output;
|
||||
if(newFrequency < MIN_FREQUENCY || newFrequency > MAX_FREQUENCY)
|
||||
{
|
||||
ErrorLogging.logError("DEBUG: Invalid MovementFacade.setFrequency() value, setting to minfrequency!");
|
||||
FREQUENCY = MIN_FREQUENCY;
|
||||
output = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
FREQUENCY = newFrequency;
|
||||
pwm.on(DUTY_CYCLE, FREQUENCY);
|
||||
output = true;
|
||||
}
|
||||
ErrorLogging.logError("DEBUG: Setting frequency to " + FREQUENCY);
|
||||
pwm.on(DUTY_CYCLE, FREQUENCY);
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the fixture's PWM frequency.
|
||||
*
|
||||
* @return The current PWM frequency.
|
||||
*/
|
||||
public int getFrequency() { return FREQUENCY; }
|
||||
|
||||
/**
|
||||
* Internal function to send the fixture to a given limit switch.
|
||||
*
|
||||
* Motor slows down after timeout is halfway through, to protect hardware.
|
||||
* Detects if the limit switch is active before activating motor.
|
||||
*
|
||||
* @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)
|
||||
{
|
||||
boolean output = false;
|
||||
FinalState output = FinalState.FAILED;
|
||||
DigitalInput limitSense;
|
||||
if(moveUp)
|
||||
{
|
||||
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...");
|
||||
}
|
||||
|
||||
double mostlyThere = (POLL_COUNT * 2) / 3;
|
||||
int slowerSpeed = FREQUENCY / 4;
|
||||
if(limitSense.isHigh()) return FinalState.SAFE;
|
||||
|
||||
pwm.on(DUTY_CYCLE, FREQUENCY);
|
||||
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;
|
||||
|
||||
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);
|
||||
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 < (POLL_COUNT);i++)
|
||||
for(int i = 0; i < highSpeedPolls; i++)
|
||||
{
|
||||
try{ Thread.sleep(POLL_WAIT); } catch(Exception e) {ErrorLogging.logError(e);};
|
||||
output = limitSense.isHigh();
|
||||
if(output) break;
|
||||
else if(i >= mostlyThere)
|
||||
{ pwm.on(DUTY_CYCLE, slowerSpeed); continue; }
|
||||
try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); }
|
||||
if(limitSense.isOn())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
if(output == false)
|
||||
ErrorLogging.logError("FIXTURE MOVEMENT ERROR! - Motor movement timed out!");
|
||||
|
||||
if(motorEnable.isOn())
|
||||
{
|
||||
output = FinalState.UNSAFE;
|
||||
pwm.on(DUTY_CYCLE, (FREQUENCY / SPEED_DOWN_FACTOR));
|
||||
for(int i = 0; i < medSpeedPolls; i++)
|
||||
{
|
||||
try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); }
|
||||
if(limitSense.isOn())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(motorEnable.isOn())
|
||||
{
|
||||
output = FinalState.SAFE;
|
||||
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); }
|
||||
if(limitSense.isOn())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
motorEnable.off();
|
||||
|
||||
pwm.on(DUTY_CYCLE, FREQUENCY);
|
||||
return output;
|
||||
}
|
||||
|
@ -444,39 +625,21 @@ public class MovementFacade
|
|||
/**
|
||||
* 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() { 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 boolean 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 boolean 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 boolean goUp() { return goUp(TIME_OUT); }
|
||||
public static FinalState goUp() { return gotoLimit(true); }
|
||||
|
||||
/**
|
||||
* 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 +651,9 @@ public class MovementFacade
|
|||
/**
|
||||
* Closes connections to all GPIO pins.
|
||||
*/
|
||||
public void closeGPIO()
|
||||
public static void closeGPIO()
|
||||
{
|
||||
goUp();
|
||||
resetArm();
|
||||
if(runSwitchThread.isAlive())
|
||||
{
|
||||
exit = true;
|
||||
|
@ -499,34 +662,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; }
|
||||
}
|
||||
|
|
|
@ -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,36 @@ 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());
|
||||
ErrorLogging.logError("DEBUG: Camera name: " + cameraName);
|
||||
newCamera(cameraName, cameraFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
18
toDoList.md
18
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.
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue