diff --git a/.github/linters/checkstyle.xml b/.github/linters/checkstyle.xml
index 9ed851b5..2cefdd3b 100644
--- a/.github/linters/checkstyle.xml
+++ b/.github/linters/checkstyle.xml
@@ -3,212 +3,191 @@
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
-
+
-
-
+
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
+
-
+
-
-
+
+
-
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
+
-
-
+
+
-
+
-
-
-
-
-
+
+
+
+
+
-
+
-
-
+
+
-
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
-
+
-
-
-
+
+
+
-
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 0640fdd1..468ab529 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -4,4 +4,4 @@ Nicholas Chang
Adrian Deutscher-Bishop
Rajit Ghosh
Debajit Ghosh
-Max Spier
\ No newline at end of file
+Max Spier
diff --git a/build.gradle b/build.gradle
index 16195267..24a2d9c0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -183,7 +183,7 @@ allprojects {
}
spotless {
- ratchetFrom 'origin/main' // only check files that have changed relative to main
+ ratchetFrom 'origin/master' // only check files that have changed relative to main
java {
target fileTree('.') {
include '**/*.java'
diff --git a/docs/sample.md b/docs/sample.md
index e04c21cd..e0ec5576 100644
--- a/docs/sample.md
+++ b/docs/sample.md
@@ -9,7 +9,7 @@
* Point 3
> Multiple
->
+>
> Line
> Blockquote
@@ -19,8 +19,8 @@
Block of Code:
public class Mechanism {
- // ...
- }
+ // ...
+ }
### Image
![Image](https://imgs.xkcd.com/comics/compiling.png)
diff --git a/docs/template.md b/docs/template.md
index 499979d9..d233ac8f 100644
--- a/docs/template.md
+++ b/docs/template.md
@@ -28,7 +28,7 @@ Explain the initialization of the mechanism and its parameters.
### Running mechanism
-How to use the mechanism? What procedures do what?
+How to use the mechanism? What procedures do what?
### Integrating mechanism with other code
diff --git a/src/main/java/com/team766/controllers/PIDController.java b/src/main/java/com/team766/controllers/PIDController.java
index c4b2b626..a1a678e1 100755
--- a/src/main/java/com/team766/controllers/PIDController.java
+++ b/src/main/java/com/team766/controllers/PIDController.java
@@ -27,308 +27,342 @@
* @author Blevenson
*
*/
-
public class PIDController {
- private int printCounter = 0;
- private boolean print = false;
-
- private final ValueProvider Kp;
- private final ValueProvider Ki;
- private final ValueProvider Kd;
- private final ValueProvider Kff;
- private final ValueProvider maxoutput_low;
- private final ValueProvider maxoutput_high;
- private final ValueProvider endthreshold;
-
- private double setpoint = Double.NaN;
-
- private boolean needsUpdate = true;
-
- private double cur_error = 0;
- private double prev_error = 0;
- private double error_rate = 0;
- private double total_error = 0;
-
- private double output_value = 0;
-
- TimeProviderI timeProvider;
- private double lastTime;
-
- public static PIDController loadFromConfig(String configPrefix) {
- if (!configPrefix.endsWith(".")) {
- configPrefix += ".";
- }
- return new PIDController(ConfigFileReader.getInstance().getDouble(configPrefix + "pGain"),
- ConfigFileReader.getInstance().getDouble(configPrefix + "iGain"),
- ConfigFileReader.getInstance().getDouble(configPrefix + "dGain"),
- ConfigFileReader.getInstance().getDouble(configPrefix + "ffGain"),
- ConfigFileReader.getInstance().getDouble(configPrefix + "outputMaxLow"),
- ConfigFileReader.getInstance().getDouble(configPrefix + "outputMaxHigh"),
- ConfigFileReader.getInstance().getDouble(configPrefix + "threshold"));
- }
-
- /**
- *
- * @param P P constant
- * @param I I constant
- * @param D D constant
- * @param outputmax_low Largest output in the negative direction
- * @param outputmax_high Largest output in the positive direction
- * @param threshold threshold for declaring the PID 'done'
- */
- public PIDController(final double P, final double I, final double D, final double outputmax_low,
- final double outputmax_high, final double threshold) {
- Kp = new SetValueProvider(P);
- Ki = new SetValueProvider(I);
- Kd = new SetValueProvider(D);
- Kff = new SetValueProvider();
- maxoutput_low = new SetValueProvider(outputmax_low);
- maxoutput_high = new SetValueProvider(outputmax_high);
- endthreshold = new SetValueProvider(threshold);
- setTimeProvider(RobotProvider.getTimeProvider());
- }
-
- public PIDController(final double P, final double I, final double D, final double FF,
- final double outputmax_low, final double outputmax_high, final double threshold) {
- this(P, I, D, outputmax_low, outputmax_high, threshold);
- ((SetValueProvider) Kff).set(FF);
- }
-
- private void setTimeProvider(final TimeProviderI timeProvider_) {
- this.timeProvider = timeProvider_;
- lastTime = timeProvider.get();
- }
-
- public PIDController(final ValueProvider P, final ValueProvider I,
- final ValueProvider D, final ValueProvider FF,
- final ValueProvider outputmax_low, final ValueProvider outputmax_high,
- final ValueProvider threshold) {
- Kp = P;
- Ki = I;
- Kd = D;
- Kff = FF;
- maxoutput_low = outputmax_low;
- maxoutput_high = outputmax_high;
- endthreshold = threshold;
- setTimeProvider(RobotProvider.getTimeProvider());
- }
-
- /**
- * Constructs a PID controller, with the specified P,I,D values, along with the end threshold.
- *
- * @param P Proportional value used in the PID controller
- * @param I Integral value used in the PID controller
- * @param D Derivative value used in the PID controller
- * @param threshold the end threshold for declaring the PID 'done'
- * @param timeProvider
- */
- public PIDController(final double P, final double I, final double D, final double threshold,
- final TimeProviderI timeProvider_) {
- Kp = new SetValueProvider(P);
- Ki = new SetValueProvider(I);
- Kd = new SetValueProvider(D);
- Kff = new SetValueProvider();
- maxoutput_low = new SetValueProvider();
- maxoutput_high = new SetValueProvider();
- endthreshold = new SetValueProvider(threshold);
- setTimeProvider(timeProvider_);
- }
-
- public PIDController(final double P, final double I, final double D, final double FF,
- final double threshold, final TimeProviderI timeProvider_) {
- Kp = new SetValueProvider(P);
- Ki = new SetValueProvider(I);
- Kd = new SetValueProvider(D);
- Kff = new SetValueProvider(FF);
- maxoutput_low = new SetValueProvider();
- maxoutput_high = new SetValueProvider();
- endthreshold = new SetValueProvider(threshold);
- setTimeProvider(timeProvider_);
- }
-
- /**
- * We may want to use same PID object, but with different setpoints, so this is separated from
- * constructor
- *
- * @param set Target point to match
- */
- public void setSetpoint(final double set) {
- setpoint = set;
- needsUpdate = true;
- }
-
- public void disable() {
- setpoint = Double.NaN;
- needsUpdate = true;
- reset();
- }
-
- /**
- * If we want to set values, such as with SmartDash
- *
- * @param P Proportional value used in the PID controller
- * @param I Integral value used in the PID controller
- * @param D Derivative value used in the PID controller
- */
- public void setConstants(final double P, final double I, final double D) {
- ((SettableValueProvider) Kp).set(P);
- ((SettableValueProvider) Ki).set(I);
- ((SettableValueProvider) Kd).set(D);
- needsUpdate = true;
- }
-
- public void setP(final double P) {
- ((SettableValueProvider) Kp).set(P);
- needsUpdate = true;
- }
-
- public void setI(final double I) {
- ((SettableValueProvider) Ki).set(I);
- needsUpdate = true;
- }
-
- public void setD(final double D) {
- ((SettableValueProvider) Kd).set(D);
- needsUpdate = true;
- }
-
- public void setFF(final double FF) {
- ((SettableValueProvider) Kff).set(FF);
- needsUpdate = true;
- }
-
- /**
- * Same as calculate() except that it prints debugging information
- *
- * @param cur_input The current input to be plugged into the PID controller
- */
- public void calculateDebug(final double cur_input) {
- print = true;
- calculate(cur_input);
- }
-
- /**
- * Calculate PID value. Run only once per loop. Use getOutput to get output.
- *
- * @param cur_input Input value from sensor
- */
- public void calculate(final double cur_input) {
- if (Double.isNaN(setpoint)) {
- // Setpoint has not been set yet.
- output_value = 0;
- return;
- }
-
- cur_error = (setpoint - cur_input);
- /*
- * if (isDone()) { output_value = 0; pr("pid done"); return; }
- */
-
- double delta_time = timeProvider.get() - lastTime;
-
- if (delta_time > 0) { // This condition basically only false in the simulator
- error_rate = (cur_error - prev_error) / delta_time;
- }
-
- total_error += cur_error * delta_time;
-
- double ki = Ki.valueOr(0.0);
- if ((total_error * ki) > 1) {
- total_error = 1 / ki;
- } else if ((total_error * ki) < -1) {
- total_error = -1 / ki;
- }
-
- double out = Kp.valueOr(0.0) * cur_error + Ki.valueOr(0.0) * total_error
- + Kd.valueOr(0.0) * error_rate + Kff.valueOr(0.0) * setpoint;
- prev_error = cur_error;
-
- pr("Pre-clip output: " + out);
-
- output_value = clip(out);
-
- needsUpdate = false;
-
- lastTime = timeProvider.get();
-
- pr(" Total Error: " + total_error + " Current Error: " + cur_error
- + " Output: " + output_value + " Setpoint: " + setpoint);
- }
-
- public double getOutput() {
- return output_value;
- }
-
- public boolean isDone() {
- final double TIME_HORIZON = 0.5;
- return !needsUpdate && Math.abs(cur_error) < endthreshold.get()
- && Math.abs(cur_error + error_rate * TIME_HORIZON) < endthreshold.get();
- }
-
- /**
- * Reset all accumulated errors
- */
- public void reset() {
- cur_error = 0;
- prev_error = 0;
- error_rate = 0;
- total_error = 0;
- lastTime = timeProvider.get();
- needsUpdate = true;
- }
-
- /**
- * Clips value for sending to motor controllers. This deals with if you don't want to run an arm
- * or wheels at full speed under PID.
- *
- * @param clipped
- * @return clipped value, safe for setting to controllers
- */
- private double clip(final double clipped) {
- double out = clipped;
- if (maxoutput_high.hasValue() && out > maxoutput_high.get()) {
- out = maxoutput_high.get();
- }
- if (maxoutput_low.hasValue() && out < maxoutput_low.get()) {
- out = maxoutput_low.get();
- }
- return out;
- }
-
- public double getError() {
- return total_error;
- }
-
- public double getCurrentError() {
- return cur_error;
- }
-
- public void setMaxoutputHigh(final Double in) {
- if (in == null) {
- ((SettableValueProvider) maxoutput_high).clear();
- } else {
- ((SettableValueProvider) maxoutput_high).set(in);
- }
- }
-
- public void setMaxoutputLow(final Double in) {
- if (in == null) {
- ((SettableValueProvider) maxoutput_low).clear();
- } else {
- ((SettableValueProvider) maxoutput_low).set(in);
- }
- }
-
- public double getSetpoint() {
- return setpoint;
- }
-
- private void pr(final Object text) {
- if (print && printCounter > 0) {
- Logger.get(Category.PID_CONTROLLER).logRaw(Severity.DEBUG, "PID: " + text);
- printCounter = 0;
- }
- printCounter++;
- }
-
+ private int printCounter = 0;
+ private boolean print = false;
+
+ private final ValueProvider Kp;
+ private final ValueProvider Ki;
+ private final ValueProvider Kd;
+ private final ValueProvider Kff;
+ private final ValueProvider maxoutput_low;
+ private final ValueProvider maxoutput_high;
+ private final ValueProvider endthreshold;
+
+ private double setpoint = Double.NaN;
+
+ private boolean needsUpdate = true;
+
+ private double cur_error = 0;
+ private double prev_error = 0;
+ private double error_rate = 0;
+ private double total_error = 0;
+
+ private double output_value = 0;
+
+ TimeProviderI timeProvider;
+ private double lastTime;
+
+ public static PIDController loadFromConfig(String configPrefix) {
+ if (!configPrefix.endsWith(".")) {
+ configPrefix += ".";
+ }
+ return new PIDController(
+ ConfigFileReader.getInstance().getDouble(configPrefix + "pGain"),
+ ConfigFileReader.getInstance().getDouble(configPrefix + "iGain"),
+ ConfigFileReader.getInstance().getDouble(configPrefix + "dGain"),
+ ConfigFileReader.getInstance().getDouble(configPrefix + "ffGain"),
+ ConfigFileReader.getInstance().getDouble(configPrefix + "outputMaxLow"),
+ ConfigFileReader.getInstance().getDouble(configPrefix + "outputMaxHigh"),
+ ConfigFileReader.getInstance().getDouble(configPrefix + "threshold"));
+ }
+
+ /**
+ *
+ * @param P P constant
+ * @param I I constant
+ * @param D D constant
+ * @param outputmax_low Largest output in the negative direction
+ * @param outputmax_high Largest output in the positive direction
+ * @param threshold threshold for declaring the PID 'done'
+ */
+ public PIDController(
+ final double P,
+ final double I,
+ final double D,
+ final double outputmax_low,
+ final double outputmax_high,
+ final double threshold) {
+ Kp = new SetValueProvider(P);
+ Ki = new SetValueProvider(I);
+ Kd = new SetValueProvider(D);
+ Kff = new SetValueProvider();
+ maxoutput_low = new SetValueProvider(outputmax_low);
+ maxoutput_high = new SetValueProvider(outputmax_high);
+ endthreshold = new SetValueProvider(threshold);
+ setTimeProvider(RobotProvider.getTimeProvider());
+ }
+
+ public PIDController(
+ final double P,
+ final double I,
+ final double D,
+ final double FF,
+ final double outputmax_low,
+ final double outputmax_high,
+ final double threshold) {
+ this(P, I, D, outputmax_low, outputmax_high, threshold);
+ ((SetValueProvider) Kff).set(FF);
+ }
+
+ private void setTimeProvider(final TimeProviderI timeProvider_) {
+ this.timeProvider = timeProvider_;
+ lastTime = timeProvider.get();
+ }
+
+ public PIDController(
+ final ValueProvider P,
+ final ValueProvider I,
+ final ValueProvider D,
+ final ValueProvider FF,
+ final ValueProvider outputmax_low,
+ final ValueProvider outputmax_high,
+ final ValueProvider threshold) {
+ Kp = P;
+ Ki = I;
+ Kd = D;
+ Kff = FF;
+ maxoutput_low = outputmax_low;
+ maxoutput_high = outputmax_high;
+ endthreshold = threshold;
+ setTimeProvider(RobotProvider.getTimeProvider());
+ }
+
+ /**
+ * Constructs a PID controller, with the specified P,I,D values, along with the end threshold.
+ *
+ * @param P Proportional value used in the PID controller
+ * @param I Integral value used in the PID controller
+ * @param D Derivative value used in the PID controller
+ * @param threshold the end threshold for declaring the PID 'done'
+ * @param timeProvider
+ */
+ public PIDController(
+ final double P,
+ final double I,
+ final double D,
+ final double threshold,
+ final TimeProviderI timeProvider_) {
+ Kp = new SetValueProvider(P);
+ Ki = new SetValueProvider(I);
+ Kd = new SetValueProvider(D);
+ Kff = new SetValueProvider();
+ maxoutput_low = new SetValueProvider();
+ maxoutput_high = new SetValueProvider();
+ endthreshold = new SetValueProvider(threshold);
+ setTimeProvider(timeProvider_);
+ }
+
+ public PIDController(
+ final double P,
+ final double I,
+ final double D,
+ final double FF,
+ final double threshold,
+ final TimeProviderI timeProvider_) {
+ Kp = new SetValueProvider(P);
+ Ki = new SetValueProvider(I);
+ Kd = new SetValueProvider(D);
+ Kff = new SetValueProvider(FF);
+ maxoutput_low = new SetValueProvider();
+ maxoutput_high = new SetValueProvider();
+ endthreshold = new SetValueProvider(threshold);
+ setTimeProvider(timeProvider_);
+ }
+
+ /**
+ * We may want to use same PID object, but with different setpoints, so this is separated from
+ * constructor
+ *
+ * @param set Target point to match
+ */
+ public void setSetpoint(final double set) {
+ setpoint = set;
+ needsUpdate = true;
+ }
+
+ public void disable() {
+ setpoint = Double.NaN;
+ needsUpdate = true;
+ reset();
+ }
+
+ /**
+ * If we want to set values, such as with SmartDash
+ *
+ * @param P Proportional value used in the PID controller
+ * @param I Integral value used in the PID controller
+ * @param D Derivative value used in the PID controller
+ */
+ public void setConstants(final double P, final double I, final double D) {
+ ((SettableValueProvider) Kp).set(P);
+ ((SettableValueProvider) Ki).set(I);
+ ((SettableValueProvider) Kd).set(D);
+ needsUpdate = true;
+ }
+
+ public void setP(final double P) {
+ ((SettableValueProvider) Kp).set(P);
+ needsUpdate = true;
+ }
+
+ public void setI(final double I) {
+ ((SettableValueProvider) Ki).set(I);
+ needsUpdate = true;
+ }
+
+ public void setD(final double D) {
+ ((SettableValueProvider) Kd).set(D);
+ needsUpdate = true;
+ }
+
+ public void setFF(final double FF) {
+ ((SettableValueProvider) Kff).set(FF);
+ needsUpdate = true;
+ }
+
+ /**
+ * Same as calculate() except that it prints debugging information
+ *
+ * @param cur_input The current input to be plugged into the PID controller
+ */
+ public void calculateDebug(final double cur_input) {
+ print = true;
+ calculate(cur_input);
+ }
+
+ /**
+ * Calculate PID value. Run only once per loop. Use getOutput to get output.
+ *
+ * @param cur_input Input value from sensor
+ */
+ public void calculate(final double cur_input) {
+ if (Double.isNaN(setpoint)) {
+ // Setpoint has not been set yet.
+ output_value = 0;
+ return;
+ }
+
+ cur_error = (setpoint - cur_input);
+ /*
+ * if (isDone()) { output_value = 0; pr("pid done"); return; }
+ */
+
+ double delta_time = timeProvider.get() - lastTime;
+
+ if (delta_time > 0) { // This condition basically only false in the simulator
+ error_rate = (cur_error - prev_error) / delta_time;
+ }
+
+ total_error += cur_error * delta_time;
+
+ double ki = Ki.valueOr(0.0);
+ if ((total_error * ki) > 1) {
+ total_error = 1 / ki;
+ } else if ((total_error * ki) < -1) {
+ total_error = -1 / ki;
+ }
+
+ double out =
+ Kp.valueOr(0.0) * cur_error
+ + Ki.valueOr(0.0) * total_error
+ + Kd.valueOr(0.0) * error_rate
+ + Kff.valueOr(0.0) * setpoint;
+ prev_error = cur_error;
+
+ pr("Pre-clip output: " + out);
+
+ output_value = clip(out);
+
+ needsUpdate = false;
+
+ lastTime = timeProvider.get();
+
+ pr(
+ " Total Error: "
+ + total_error
+ + " Current Error: "
+ + cur_error
+ + " Output: "
+ + output_value
+ + " Setpoint: "
+ + setpoint);
+ }
+
+ public double getOutput() {
+ return output_value;
+ }
+
+ public boolean isDone() {
+ final double TIME_HORIZON = 0.5;
+ return !needsUpdate
+ && Math.abs(cur_error) < endthreshold.get()
+ && Math.abs(cur_error + error_rate * TIME_HORIZON) < endthreshold.get();
+ }
+
+ /**
+ * Reset all accumulated errors
+ */
+ public void reset() {
+ cur_error = 0;
+ prev_error = 0;
+ error_rate = 0;
+ total_error = 0;
+ lastTime = timeProvider.get();
+ needsUpdate = true;
+ }
+
+ /**
+ * Clips value for sending to motor controllers. This deals with if you don't want to run an arm
+ * or wheels at full speed under PID.
+ *
+ * @param clipped
+ * @return clipped value, safe for setting to controllers
+ */
+ private double clip(final double clipped) {
+ double out = clipped;
+ if (maxoutput_high.hasValue() && out > maxoutput_high.get()) {
+ out = maxoutput_high.get();
+ }
+ if (maxoutput_low.hasValue() && out < maxoutput_low.get()) {
+ out = maxoutput_low.get();
+ }
+ return out;
+ }
+
+ public double getError() {
+ return total_error;
+ }
+
+ public double getCurrentError() {
+ return cur_error;
+ }
+
+ public void setMaxoutputHigh(final Double in) {
+ if (in == null) {
+ ((SettableValueProvider) maxoutput_high).clear();
+ } else {
+ ((SettableValueProvider) maxoutput_high).set(in);
+ }
+ }
+
+ public void setMaxoutputLow(final Double in) {
+ if (in == null) {
+ ((SettableValueProvider) maxoutput_low).clear();
+ } else {
+ ((SettableValueProvider) maxoutput_low).set(in);
+ }
+ }
+
+ public double getSetpoint() {
+ return setpoint;
+ }
+
+ private void pr(final Object text) {
+ if (print && printCounter > 0) {
+ Logger.get(Category.PID_CONTROLLER).logRaw(Severity.DEBUG, "PID: " + text);
+ printCounter = 0;
+ }
+ printCounter++;
+ }
}
diff --git a/src/main/java/com/team766/hal/CanivPoller.java b/src/main/java/com/team766/hal/CanivPoller.java
index 27048eae..8b36c193 100644
--- a/src/main/java/com/team766/hal/CanivPoller.java
+++ b/src/main/java/com/team766/hal/CanivPoller.java
@@ -1,81 +1,82 @@
package com.team766.hal;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
import com.team766.logging.Category;
import com.team766.logging.Logger;
import com.team766.logging.Severity;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.networktables.StringPublisher;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
public class CanivPoller implements Runnable {
- private static final String CANIV_PATH = "/usr/bin";
- public static final String CANIV_BIN = CANIV_PATH + "/" + "caniv";
- private static final String CANIV_ARGS[] = { CANIV_BIN, "-a", "-i" }; // because, AI
+ private static final String CANIV_PATH = "/usr/bin";
+ public static final String CANIV_BIN = CANIV_PATH + "/" + "caniv";
+ private static final String CANIV_ARGS[] = {CANIV_BIN, "-a", "-i"}; // because, AI
- private final Logger logger = Logger.get(Category.DRIVE);
+ private final Logger logger = Logger.get(Category.DRIVE);
- private final long periodMillis;
- private final ProcessBuilder processBuilder;
- private final AtomicBoolean done = new AtomicBoolean(false);
- private final NetworkTableInstance nti = NetworkTableInstance.getDefault();
- private final NetworkTable table = nti.getTable("caniv");
- private final Map publishers = new HashMap();
+ private final long periodMillis;
+ private final ProcessBuilder processBuilder;
+ private final AtomicBoolean done = new AtomicBoolean(false);
+ private final NetworkTableInstance nti = NetworkTableInstance.getDefault();
+ private final NetworkTable table = nti.getTable("caniv");
+ private final Map publishers = new HashMap();
- public CanivPoller(long periodMillis) {
- this.periodMillis = periodMillis;
- this.processBuilder = new ProcessBuilder(CANIV_ARGS);
- }
+ public CanivPoller(long periodMillis) {
+ this.periodMillis = periodMillis;
+ this.processBuilder = new ProcessBuilder(CANIV_ARGS);
+ }
- @Override
- public void run() {
- while (!done.get()) {
- try {
- Process process = processBuilder.start();
- InputStream response = process.getInputStream();
- BufferedReader reader = new BufferedReader(new InputStreamReader(response));
- logger.logRaw(Severity.INFO, "caniv");
- String line = null;
- while ((line = reader.readLine()) != null) {
- if (line.isEmpty()) continue;
- logger.logRaw(Severity.INFO, line);
- String[] keyValue = line.split(":", 2);
- if (keyValue.length != 2) continue;
- keyValue[0] = keyValue[0].trim();
- keyValue[1] = keyValue[1].trim();
+ @Override
+ public void run() {
+ while (!done.get()) {
+ try {
+ Process process = processBuilder.start();
+ InputStream response = process.getInputStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(response));
+ logger.logRaw(Severity.INFO, "caniv");
+ String line = null;
+ while ((line = reader.readLine()) != null) {
+ if (line.isEmpty()) continue;
+ logger.logRaw(Severity.INFO, line);
+ String[] keyValue = line.split(":", 2);
+ if (keyValue.length != 2) continue;
+ keyValue[0] = keyValue[0].trim();
+ keyValue[1] = keyValue[1].trim();
- if (!publishers.containsKey(keyValue[0])) {
- publishers.put(keyValue[0], table.getStringTopic(keyValue[0]).publish());
- }
- publishers.get(keyValue[0]).set(keyValue[1]);
- }
- response.close();
+ if (!publishers.containsKey(keyValue[0])) {
+ publishers.put(keyValue[0], table.getStringTopic(keyValue[0]).publish());
+ }
+ publishers.get(keyValue[0]).set(keyValue[1]);
+ }
+ response.close();
- process.destroy();
+ process.destroy();
- } catch (Exception e) {
- logger.logRaw(Severity.ERROR, "Exception caught trying to execute or parse output from caniv: "
- + e.getMessage());
- e.printStackTrace();
- }
+ } catch (Exception e) {
+ logger.logRaw(
+ Severity.ERROR,
+ "Exception caught trying to execute or parse output from caniv: "
+ + e.getMessage());
+ e.printStackTrace();
+ }
- try {
- Thread.sleep(periodMillis); // TODO: measure execution time, adjust sleep.
- } catch (Exception e) {
- logger.logRaw(Severity.ERROR, "Exception caught trying to sleep: "
- + e.getMessage());
- e.printStackTrace();
- }
- }
- }
+ try {
+ Thread.sleep(periodMillis); // TODO: measure execution time, adjust sleep.
+ } catch (Exception e) {
+ logger.logRaw(
+ Severity.ERROR, "Exception caught trying to sleep: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+ }
- public void setDone(boolean done) {
- this.done.set(done);
- }
+ public void setDone(boolean done) {
+ this.done.set(done);
+ }
}
diff --git a/src/main/java/com/team766/hal/GenericRobotMain.java b/src/main/java/com/team766/hal/GenericRobotMain.java
index 8e76425d..c48a72a5 100755
--- a/src/main/java/com/team766/hal/GenericRobotMain.java
+++ b/src/main/java/com/team766/hal/GenericRobotMain.java
@@ -1,10 +1,10 @@
package com.team766.hal;
-import com.team766.framework.Scheduler;
import com.team766.framework.AutonomousMode;
import com.team766.framework.LaunchedContext;
import com.team766.framework.Procedure;
// import com.team766.hal.GenericRobotMain;
+import com.team766.framework.Scheduler;
import com.team766.logging.Category;
import com.team766.logging.Logger;
import com.team766.logging.Severity;
@@ -22,146 +22,150 @@
// Team 766 - Robot Interface Base class
public final class GenericRobotMain {
- private OI m_oi;
-
- private WebServer m_webServer;
- private AutonomousSelector m_autonSelector;
- private AutonomousMode m_autonMode = null;
- private LaunchedContext m_autonomous = null;
- private LaunchedContext m_oiContext = null;
-
- // Reset the autonomous routine if the robot is disabled for more than this
- // number of seconds.
- private static final double RESET_IN_DISABLED_PERIOD = 10.0;
- private double m_disabledModeStartTime;
-
- private boolean faultInRobotInit = false;
- private boolean faultInAutoInit = false;
- private boolean faultInTeleopInit = false;
-
- public GenericRobotMain() {
- Scheduler.getInstance().reset();
-
- m_autonSelector = new AutonomousSelector(AutonomousModes.AUTONOMOUS_MODES);
- m_webServer = new WebServer();
- m_webServer.addHandler(new Dashboard());
- m_webServer.addHandler(new DriverInterface(m_autonSelector));
- m_webServer.addHandler(new ConfigUI());
- m_webServer.addHandler(new LogViewer());
- m_webServer.addHandler(new ReadLogs());
- m_webServer.addHandler(m_autonSelector);
- m_webServer.start();
- }
-
- public void robotInit() {
- try {
- Robot.robotInit();
-
- m_oi = new OI();
- } catch (Throwable ex) {
- faultInRobotInit = true;
- throw ex;
- }
- faultInRobotInit = false;
- }
-
- public void disabledInit() {
- m_disabledModeStartTime = RobotProvider.instance.getClock().getTime();
- }
-
- public void disabledPeriodic() {
- if (faultInRobotInit) return;
-
- // The robot can enter disabled mode for two reasons:
- // - The field control system set the robots to disabled.
- // - The robot loses communication with the driver station.
- // In the former case, we want to reset the autonomous routine, as there
- // may have been a field fault, which would mean the match is going to
- // be replayed (and thus we would want to run the autonomous routine
- // from the beginning). In the latter case, we don't want to reset the
- // autonomous routine because the communication drop was likely caused
- // by some short-lived (less than a second long, or so) interference;
- // when the communications are restored, we want to continue executing
- // the routine that was interrupted, since it has knowledge of where the
- // robot is on the field, the state of the robot's mechanisms, etc.
- // Thus, we set a threshold on the amount of time spent in autonomous of
- // 10 seconds. It is almost certain that it will take longer than 10
- // seconds to reset the field if a match is to be replayed, but it is
- // also almost certain that a communication drop will be much shorter
- // than 10 seconds.
- double timeInState =
- RobotProvider.instance.getClock().getTime() - m_disabledModeStartTime;
- if (timeInState > RESET_IN_DISABLED_PERIOD) {
- resetAutonomousMode("time in disabled mode");
- }
- Scheduler.getInstance().run();
- }
-
- public void resetAutonomousMode(final String reason) {
- if (m_autonomous != null) {
- m_autonomous.stop();
- m_autonomous = null;
- m_autonMode = null;
- Logger.get(Category.AUTONOMOUS).logRaw(
- Severity.INFO, "Resetting autonomus procedure from " + reason);
- }
- }
-
- public void autonomousInit() {
- faultInAutoInit = true;
-
- if (m_oiContext != null) {
- m_oiContext.stop();
- m_oiContext = null;
- }
-
- Robot.gyro.resetGyro180();
-// Robot.drive.setGyro(Robot.gyro.getGyroYaw());
-
- if (m_autonomous != null) {
- Logger.get(Category.AUTONOMOUS).logRaw(Severity.INFO, "Continuing previous autonomus procedure " + m_autonomous.getContextName());
- } else if (m_autonSelector.getSelectedAutonMode() == null) {
- Logger.get(Category.AUTONOMOUS).logRaw(Severity.WARNING, "No autonomous mode selected");
- }
- faultInAutoInit = false;
- }
-
- public void autonomousPeriodic() {
- if (faultInRobotInit || faultInAutoInit) return;
-
- final AutonomousMode autonomousMode = m_autonSelector.getSelectedAutonMode();
- if (autonomousMode != null && m_autonMode != autonomousMode) {
- final Procedure autonProcedure = autonomousMode.instantiate();
- m_autonomous = Scheduler.getInstance().startAsync(autonProcedure);
- m_autonMode = autonomousMode;
- Logger.get(Category.AUTONOMOUS).logRaw(Severity.INFO, "Starting new autonomus procedure " + autonProcedure.getName());
- }
- Scheduler.getInstance().run();
- }
-
- public void teleopInit() {
- faultInTeleopInit = true;
-
- if (m_autonomous != null) {
- m_autonomous.stop();
- m_autonomous = null;
- m_autonMode = null;
- }
-
- if (m_oiContext == null && m_oi != null) {
- m_oiContext = Scheduler.getInstance().startAsync(m_oi);
- }
-
- faultInTeleopInit = false;
- }
-
- public void teleopPeriodic() {
- if (faultInRobotInit || faultInTeleopInit) return;
-
- if (m_oiContext != null && m_oiContext.isDone()) {
- m_oiContext = Scheduler.getInstance().startAsync(m_oi);
- Logger.get(Category.OPERATOR_INTERFACE).logRaw(Severity.WARNING, "Restarting OI context");
- }
- Scheduler.getInstance().run();
- }
+ private OI m_oi;
+
+ private WebServer m_webServer;
+ private AutonomousSelector m_autonSelector;
+ private AutonomousMode m_autonMode = null;
+ private LaunchedContext m_autonomous = null;
+ private LaunchedContext m_oiContext = null;
+
+ // Reset the autonomous routine if the robot is disabled for more than this
+ // number of seconds.
+ private static final double RESET_IN_DISABLED_PERIOD = 10.0;
+ private double m_disabledModeStartTime;
+
+ private boolean faultInRobotInit = false;
+ private boolean faultInAutoInit = false;
+ private boolean faultInTeleopInit = false;
+
+ public GenericRobotMain() {
+ Scheduler.getInstance().reset();
+
+ m_autonSelector = new AutonomousSelector(AutonomousModes.AUTONOMOUS_MODES);
+ m_webServer = new WebServer();
+ m_webServer.addHandler(new Dashboard());
+ m_webServer.addHandler(new DriverInterface(m_autonSelector));
+ m_webServer.addHandler(new ConfigUI());
+ m_webServer.addHandler(new LogViewer());
+ m_webServer.addHandler(new ReadLogs());
+ m_webServer.addHandler(m_autonSelector);
+ m_webServer.start();
+ }
+
+ public void robotInit() {
+ try {
+ Robot.robotInit();
+
+ m_oi = new OI();
+ } catch (Throwable ex) {
+ faultInRobotInit = true;
+ throw ex;
+ }
+ faultInRobotInit = false;
+ }
+
+ public void disabledInit() {
+ m_disabledModeStartTime = RobotProvider.instance.getClock().getTime();
+ }
+
+ public void disabledPeriodic() {
+ if (faultInRobotInit) return;
+
+ // The robot can enter disabled mode for two reasons:
+ // - The field control system set the robots to disabled.
+ // - The robot loses communication with the driver station.
+ // In the former case, we want to reset the autonomous routine, as there
+ // may have been a field fault, which would mean the match is going to
+ // be replayed (and thus we would want to run the autonomous routine
+ // from the beginning). In the latter case, we don't want to reset the
+ // autonomous routine because the communication drop was likely caused
+ // by some short-lived (less than a second long, or so) interference;
+ // when the communications are restored, we want to continue executing
+ // the routine that was interrupted, since it has knowledge of where the
+ // robot is on the field, the state of the robot's mechanisms, etc.
+ // Thus, we set a threshold on the amount of time spent in autonomous of
+ // 10 seconds. It is almost certain that it will take longer than 10
+ // seconds to reset the field if a match is to be replayed, but it is
+ // also almost certain that a communication drop will be much shorter
+ // than 10 seconds.
+ double timeInState = RobotProvider.instance.getClock().getTime() - m_disabledModeStartTime;
+ if (timeInState > RESET_IN_DISABLED_PERIOD) {
+ resetAutonomousMode("time in disabled mode");
+ }
+ Scheduler.getInstance().run();
+ }
+
+ public void resetAutonomousMode(final String reason) {
+ if (m_autonomous != null) {
+ m_autonomous.stop();
+ m_autonomous = null;
+ m_autonMode = null;
+ Logger.get(Category.AUTONOMOUS)
+ .logRaw(Severity.INFO, "Resetting autonomus procedure from " + reason);
+ }
+ }
+
+ public void autonomousInit() {
+ faultInAutoInit = true;
+
+ if (m_oiContext != null) {
+ m_oiContext.stop();
+ m_oiContext = null;
+ }
+
+ if (m_autonomous != null) {
+ Logger.get(Category.AUTONOMOUS)
+ .logRaw(
+ Severity.INFO,
+ "Continuing previous autonomus procedure "
+ + m_autonomous.getContextName());
+ } else if (m_autonSelector.getSelectedAutonMode() == null) {
+ Logger.get(Category.AUTONOMOUS).logRaw(Severity.WARNING, "No autonomous mode selected");
+ }
+ faultInAutoInit = false;
+ }
+
+ public void autonomousPeriodic() {
+ if (faultInRobotInit || faultInAutoInit) return;
+
+ final AutonomousMode autonomousMode = m_autonSelector.getSelectedAutonMode();
+ if (autonomousMode != null && m_autonMode != autonomousMode) {
+ final Procedure autonProcedure = autonomousMode.instantiate();
+ m_autonomous = Scheduler.getInstance().startAsync(autonProcedure);
+ m_autonMode = autonomousMode;
+ Logger.get(Category.AUTONOMOUS)
+ .logRaw(
+ Severity.INFO,
+ "Starting new autonomus procedure " + autonProcedure.getName());
+ }
+ Scheduler.getInstance().run();
+ }
+
+ public void teleopInit() {
+ faultInTeleopInit = true;
+
+ if (m_autonomous != null) {
+ m_autonomous.stop();
+ m_autonomous = null;
+ m_autonMode = null;
+ }
+
+ if (m_oiContext == null && m_oi != null) {
+ m_oiContext = Scheduler.getInstance().startAsync(m_oi);
+ }
+
+ faultInTeleopInit = false;
+ }
+
+ public void teleopPeriodic() {
+ if (faultInRobotInit || faultInTeleopInit) return;
+
+ if (m_oiContext != null && m_oiContext.isDone()) {
+ m_oiContext = Scheduler.getInstance().startAsync(m_oi);
+ Logger.get(Category.OPERATOR_INTERFACE)
+ .logRaw(Severity.WARNING, "Restarting OI context");
+ }
+ Scheduler.getInstance().run();
+ }
}
diff --git a/src/main/java/com/team766/hal/RobotProvider.java b/src/main/java/com/team766/hal/RobotProvider.java
index 74dec9f7..03256b48 100755
--- a/src/main/java/com/team766/hal/RobotProvider.java
+++ b/src/main/java/com/team766/hal/RobotProvider.java
@@ -1,267 +1,312 @@
package com.team766.hal;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.io.StringWriter;
-import java.io.PrintWriter;
-
import com.team766.config.ConfigFileReader;
import com.team766.controllers.TimeProviderI;
import com.team766.hal.mock.MockAnalogInput;
import com.team766.hal.mock.MockDigitalInput;
import com.team766.hal.mock.MockEncoder;
import com.team766.hal.mock.MockGyro;
-import com.team766.hal.mock.MockRelay;
import com.team766.hal.mock.MockMotorController;
+import com.team766.hal.mock.MockRelay;
import com.team766.library.ValueProvider;
import com.team766.logging.Category;
import com.team766.logging.Logger;
import com.team766.logging.LoggerExceptionUtils;
import com.team766.logging.Severity;
+import java.util.Arrays;
+import java.util.HashMap;
public abstract class RobotProvider {
- public static RobotProvider instance;
-
- protected EncoderReader[] encoders = new EncoderReader[20];
- protected SolenoidController[] solenoids = new SolenoidController[20];
- protected GyroReader[] gyros = new GyroReader[13];
- protected HashMap cams = new HashMap();
- protected JoystickReader[] joysticks = new JoystickReader[8];
- protected DigitalInputReader[] digInputs = new DigitalInputReader[8];
- protected AnalogInputReader[] angInputs = new AnalogInputReader[5];
- protected RelayOutput[] relays = new RelayOutput[5];
- protected PositionReader positionSensor = null;
- protected BeaconReader beaconSensor = null;
-
- private HashMap motorDeviceIdNames = new HashMap();
- private HashMap motorPortNames = new HashMap();
- private HashMap digitalIoNames = new HashMap();
- private HashMap analogInputNames = new HashMap();
- private HashMap relayNames = new HashMap();
- private HashMap solenoidNames = new HashMap();
- private HashMap gyroNames = new HashMap();
-
- //HAL
- protected abstract MotorController getMotor(int index, String configPrefix, MotorController.Type type, ControlInputReader localSensor);
-
- public abstract EncoderReader getEncoder(int index1, int index2);
-
- public abstract DigitalInputReader getDigitalInput(int index);
-
- public abstract AnalogInputReader getAnalogInput(int index);
-
- public abstract RelayOutput getRelay(int index);
-
- public abstract SolenoidController getSolenoid(int index);
-
- public abstract GyroReader getGyro(int index);
-
- public abstract CameraReader getCamera(String id, String value);
-
- public abstract PositionReader getPositionSensor();
-
- public abstract BeaconReader getBeaconSensor();
-
- public static TimeProviderI getTimeProvider() {
- return () -> instance.getClock().getTime();
- }
-
- // Config-driven methods
-
- private void checkDeviceName(final String deviceType,
- final HashMap deviceNames, final Integer portId,
- final String configName) {
- String previousName = deviceNames.putIfAbsent(portId, configName);
- if (previousName != null && previousName != configName) {
- Logger.get(Category.CONFIGURATION).logRaw(Severity.ERROR, "Multiple " + deviceType
- + " devices for port ID " + portId + ": " + previousName + ", " + configName);
- }
- }
-
- public MotorController getMotor(final String configName) {
- final String encoderConfigName = configName + ".encoder";
- final String analogInputConfigName = configName + ".analogInput";
- final ControlInputReader sensor =
- ConfigFileReader.getInstance().containsKey(encoderConfigName)
- ? getEncoder(encoderConfigName)
- : ConfigFileReader.getInstance().containsKey(analogInputConfigName)
- ? getAnalogInput(analogInputConfigName)
- : null;
-
- try {
- ValueProvider deviceId =
- ConfigFileReader.getInstance().getInt(configName + ".deviceId");
- final ValueProvider port =
- ConfigFileReader.getInstance().getInt(configName + ".port");
- final ValueProvider sensorScaleConfig =
- ConfigFileReader.getInstance().getDouble(configName + ".sensorScale");
- final ValueProvider invertedConfig =
- ConfigFileReader.getInstance().getBoolean(configName + ".inverted");
- final ValueProvider sensorInvertedConfig =
- ConfigFileReader.getInstance().getBoolean(configName + ".sensorInverted");
- final ValueProvider type = ConfigFileReader.getInstance()
- .getEnum(MotorController.Type.class, configName + ".type");
-
- if (deviceId.hasValue() && port.hasValue()) {
- Logger.get(Category.CONFIGURATION).logData(Severity.ERROR,
- "Motor %s configuration should have only one of `deviceId` or `port`",
- configName);
- }
-
- MotorController.Type defaultType = MotorController.Type.TalonSRX;
- if (!deviceId.hasValue()) {
- deviceId = port;
- defaultType = MotorController.Type.VictorSP;
- checkDeviceName("PWM motor controller", motorPortNames, port.get(), configName);
- } else {
- checkDeviceName("CAN motor controller", motorDeviceIdNames, deviceId.get(),
- configName);
- }
-
- var motor = getMotor(deviceId.get(), configName, type.valueOr(defaultType), sensor);
- if (sensorScaleConfig.hasValue()) {
- motor = new MotorControllerWithSensorScale(motor, sensorScaleConfig.get());
- }
- if (invertedConfig.valueOr(false)) {
- motor.setInverted(true);
- }
- if (sensorInvertedConfig.valueOr(false)) {
- motor.setSensorInverted(true);
- }
- return motor;
- } catch (IllegalArgumentException ex) {
- Logger.get(Category.CONFIGURATION).logData(Severity.ERROR,
- "Error getting configuration for motor %s from config file, using mock motor instead.\nDetailed error: %s",
- configName, LoggerExceptionUtils.exceptionToString(ex));
- return new LocalMotorController(configName, new MockMotorController(0), sensor);
- }
- }
-
- public EncoderReader getEncoder(final String configName) {
- try {
- final ValueProvider ports =
- ConfigFileReader.getInstance().getInts(configName + ".ports");
- final ValueProvider distancePerPulseConfig =
- ConfigFileReader.getInstance().getDouble(configName + ".distancePerPulse");
-
- final var portsValue = ports.get();
- if (portsValue.length != 2) {
- Logger.get(Category.CONFIGURATION).logData(Severity.ERROR,
- "Encoder %s has %d config values, but expected 2", configName,
- portsValue.length);
- return new MockEncoder(0, 0);
- }
- checkDeviceName("encoder/digital input", digitalIoNames, portsValue[0], configName);
- checkDeviceName("encoder/digital input", digitalIoNames, portsValue[1], configName);
- final EncoderReader reader = getEncoder(portsValue[0], portsValue[1]);
- if (distancePerPulseConfig.hasValue()) {
- reader.setDistancePerPulse(distancePerPulseConfig.get());
- }
- return reader;
- } catch (IllegalArgumentException ex) {
- Logger.get(Category.CONFIGURATION).logData(Severity.ERROR,
- "Encoder %s not found in config file, using mock encoder instead", configName);
- return new MockEncoder(0, 0);
- }
- }
-
- public DigitalInputReader getDigitalInput(final String configName) {
- try {
- ValueProvider port =
- ConfigFileReader.getInstance().getInt(configName + ".port");
- checkDeviceName("encoder/digital input", digitalIoNames, port.get(), configName);
-
- return getDigitalInput(port.get());
- } catch (IllegalArgumentException ex) {
- Logger.get(Category.CONFIGURATION).logData(Severity.ERROR,
- "Digital input %s not found in config file, using mock digital input instead",
- configName);
- return new MockDigitalInput();
- }
- }
-
- public AnalogInputReader getAnalogInput(final String configName) {
- try {
- ValueProvider port =
- ConfigFileReader.getInstance().getInt(configName + ".port");
- checkDeviceName("analog input", analogInputNames, port.get(), configName);
-
- return getAnalogInput(port.get());
- } catch (IllegalArgumentException ex) {
- Logger.get(Category.CONFIGURATION).logData(Severity.ERROR,
- "Analog input %s not found in config file, using mock analog input instead",
- configName);
- return new MockAnalogInput();
- }
- }
-
- public RelayOutput getRelay(final String configName) {
- try {
- ValueProvider port =
- ConfigFileReader.getInstance().getInt(configName + ".port");
- checkDeviceName("relay", relayNames, port.get(), configName);
-
- return getRelay(port.get());
- } catch (IllegalArgumentException ex) {
- Logger.get(Category.CONFIGURATION).logData(Severity.ERROR,
- "Relay %s not found in config file, using mock relay instead", configName);
- return new MockRelay(0);
- }
- }
-
- public DoubleSolenoid getSolenoid(final String configName) {
- try {
- final String legacyConfigKey = configName + ".port";
- ValueProvider forwardPorts =
- ConfigFileReader.getInstance().containsKey(legacyConfigKey)
- ? ConfigFileReader.getInstance().getInts(legacyConfigKey)
- : ConfigFileReader.getInstance().getInts(configName + ".forwardPort");
- ValueProvider reversePorts =
- ConfigFileReader.getInstance().getInts(configName + ".reversePort");
-
- for (Integer port : forwardPorts.valueOr(new Integer[0])) {
- checkDeviceName("solenoid", solenoidNames, port, configName);
- }
- for (Integer port : reversePorts.valueOr(new Integer[0])) {
- checkDeviceName("solenoid", solenoidNames, port, configName);
- }
-
- SolenoidController forwardSolenoids = new MultiSolenoid(Arrays
- .stream(forwardPorts.valueOr(new Integer[0]))
- .map(this::getSolenoid).toArray(SolenoidController[]::new));
- SolenoidController reverseSolenoids = new MultiSolenoid(Arrays
- .stream(reversePorts.valueOr(new Integer[0]))
- .map(this::getSolenoid).toArray(SolenoidController[]::new));
- return new DoubleSolenoid(forwardSolenoids, reverseSolenoids);
- } catch (IllegalArgumentException ex) {
- Logger.get(Category.CONFIGURATION).logData(Severity.ERROR, "Solenoid %s not found in config file, using mock solenoid instead %s", configName, ex.toString());
- return new DoubleSolenoid(null, null);
- }
- }
-
- public GyroReader getGyro(final String configName) {
- try {
- ValueProvider port = ConfigFileReader.getInstance().getInt(configName + ".port");
- checkDeviceName("gyro", gyroNames, port.get(), configName);
-
- return getGyro(port.get());
- } catch (IllegalArgumentException ex) {
- Logger.get(Category.CONFIGURATION).logData(Severity.ERROR, "Gyro %s not found in config file, using mock gyro instead", configName);
- return new MockGyro();
- }
- }
-
- //Operator Devices
- public abstract JoystickReader getJoystick(int index);
-
- public abstract CameraInterface getCamServer();
-
- public abstract Clock getClock();
-
- public abstract double getBatteryVoltage();
-
- public abstract void refreshDriverStationData();
-
- public abstract boolean hasNewDriverStationData();
+ public static RobotProvider instance;
+
+ protected EncoderReader[] encoders = new EncoderReader[20];
+ protected SolenoidController[] solenoids = new SolenoidController[20];
+ protected GyroReader[] gyros = new GyroReader[13];
+ protected HashMap cams = new HashMap();
+ protected JoystickReader[] joysticks = new JoystickReader[8];
+ protected DigitalInputReader[] digInputs = new DigitalInputReader[8];
+ protected AnalogInputReader[] angInputs = new AnalogInputReader[5];
+ protected RelayOutput[] relays = new RelayOutput[5];
+ protected PositionReader positionSensor = null;
+ protected BeaconReader beaconSensor = null;
+
+ private HashMap motorDeviceIdNames = new HashMap();
+ private HashMap motorPortNames = new HashMap();
+ private HashMap digitalIoNames = new HashMap();
+ private HashMap analogInputNames = new HashMap();
+ private HashMap relayNames = new HashMap();
+ private HashMap solenoidNames = new HashMap();
+ private HashMap gyroNames = new HashMap();
+
+ // HAL
+ protected abstract MotorController getMotor(
+ int index,
+ String configPrefix,
+ MotorController.Type type,
+ ControlInputReader localSensor);
+
+ public abstract EncoderReader getEncoder(int index1, int index2);
+
+ public abstract DigitalInputReader getDigitalInput(int index);
+
+ public abstract AnalogInputReader getAnalogInput(int index);
+
+ public abstract RelayOutput getRelay(int index);
+
+ public abstract SolenoidController getSolenoid(int index);
+
+ public abstract GyroReader getGyro(int index);
+
+ public abstract CameraReader getCamera(String id, String value);
+
+ public abstract PositionReader getPositionSensor();
+
+ public abstract BeaconReader getBeaconSensor();
+
+ public static TimeProviderI getTimeProvider() {
+ return () -> instance.getClock().getTime();
+ }
+
+ // Config-driven methods
+
+ private void checkDeviceName(
+ final String deviceType,
+ final HashMap deviceNames,
+ final Integer portId,
+ final String configName) {
+ String previousName = deviceNames.putIfAbsent(portId, configName);
+ if (previousName != null && previousName != configName) {
+ Logger.get(Category.CONFIGURATION)
+ .logRaw(
+ Severity.ERROR,
+ "Multiple "
+ + deviceType
+ + " devices for port ID "
+ + portId
+ + ": "
+ + previousName
+ + ", "
+ + configName);
+ }
+ }
+
+ public MotorController getMotor(final String configName) {
+ final String encoderConfigName = configName + ".encoder";
+ final String analogInputConfigName = configName + ".analogInput";
+ final ControlInputReader sensor =
+ ConfigFileReader.getInstance().containsKey(encoderConfigName)
+ ? getEncoder(encoderConfigName)
+ : ConfigFileReader.getInstance().containsKey(analogInputConfigName)
+ ? getAnalogInput(analogInputConfigName)
+ : null;
+
+ try {
+ ValueProvider deviceId =
+ ConfigFileReader.getInstance().getInt(configName + ".deviceId");
+ final ValueProvider port =
+ ConfigFileReader.getInstance().getInt(configName + ".port");
+ final ValueProvider sensorScaleConfig =
+ ConfigFileReader.getInstance().getDouble(configName + ".sensorScale");
+ final ValueProvider invertedConfig =
+ ConfigFileReader.getInstance().getBoolean(configName + ".inverted");
+ final ValueProvider sensorInvertedConfig =
+ ConfigFileReader.getInstance().getBoolean(configName + ".sensorInverted");
+ final ValueProvider type =
+ ConfigFileReader.getInstance()
+ .getEnum(MotorController.Type.class, configName + ".type");
+
+ if (deviceId.hasValue() && port.hasValue()) {
+ Logger.get(Category.CONFIGURATION)
+ .logData(
+ Severity.ERROR,
+ "Motor %s configuration should have only one of `deviceId` or `port`",
+ configName);
+ }
+
+ MotorController.Type defaultType = MotorController.Type.TalonSRX;
+ if (!deviceId.hasValue()) {
+ deviceId = port;
+ defaultType = MotorController.Type.VictorSP;
+ checkDeviceName("PWM motor controller", motorPortNames, port.get(), configName);
+ } else {
+ checkDeviceName(
+ "CAN motor controller", motorDeviceIdNames, deviceId.get(), configName);
+ }
+
+ var motor = getMotor(deviceId.get(), configName, type.valueOr(defaultType), sensor);
+ if (sensorScaleConfig.hasValue()) {
+ motor = new MotorControllerWithSensorScale(motor, sensorScaleConfig.get());
+ }
+ if (invertedConfig.valueOr(false)) {
+ motor.setInverted(true);
+ }
+ if (sensorInvertedConfig.valueOr(false)) {
+ motor.setSensorInverted(true);
+ }
+ return motor;
+ } catch (IllegalArgumentException ex) {
+ Logger.get(Category.CONFIGURATION)
+ .logData(
+ Severity.ERROR,
+ "Error getting configuration for motor %s from config file, using mock motor instead.\nDetailed error: %s",
+ configName,
+ LoggerExceptionUtils.exceptionToString(ex));
+ return new LocalMotorController(configName, new MockMotorController(0), sensor);
+ }
+ }
+
+ public EncoderReader getEncoder(final String configName) {
+ try {
+ final ValueProvider ports =
+ ConfigFileReader.getInstance().getInts(configName + ".ports");
+ final ValueProvider distancePerPulseConfig =
+ ConfigFileReader.getInstance().getDouble(configName + ".distancePerPulse");
+
+ final var portsValue = ports.get();
+ if (portsValue.length != 2) {
+ Logger.get(Category.CONFIGURATION)
+ .logData(
+ Severity.ERROR,
+ "Encoder %s has %d config values, but expected 2",
+ configName,
+ portsValue.length);
+ return new MockEncoder(0, 0);
+ }
+ checkDeviceName("encoder/digital input", digitalIoNames, portsValue[0], configName);
+ checkDeviceName("encoder/digital input", digitalIoNames, portsValue[1], configName);
+ final EncoderReader reader = getEncoder(portsValue[0], portsValue[1]);
+ if (distancePerPulseConfig.hasValue()) {
+ reader.setDistancePerPulse(distancePerPulseConfig.get());
+ }
+ return reader;
+ } catch (IllegalArgumentException ex) {
+ Logger.get(Category.CONFIGURATION)
+ .logData(
+ Severity.ERROR,
+ "Encoder %s not found in config file, using mock encoder instead",
+ configName);
+ return new MockEncoder(0, 0);
+ }
+ }
+
+ public DigitalInputReader getDigitalInput(final String configName) {
+ try {
+ ValueProvider port =
+ ConfigFileReader.getInstance().getInt(configName + ".port");
+ checkDeviceName("encoder/digital input", digitalIoNames, port.get(), configName);
+
+ return getDigitalInput(port.get());
+ } catch (IllegalArgumentException ex) {
+ Logger.get(Category.CONFIGURATION)
+ .logData(
+ Severity.ERROR,
+ "Digital input %s not found in config file, using mock digital input instead",
+ configName);
+ return new MockDigitalInput();
+ }
+ }
+
+ public AnalogInputReader getAnalogInput(final String configName) {
+ try {
+ ValueProvider port =
+ ConfigFileReader.getInstance().getInt(configName + ".port");
+ checkDeviceName("analog input", analogInputNames, port.get(), configName);
+
+ return getAnalogInput(port.get());
+ } catch (IllegalArgumentException ex) {
+ Logger.get(Category.CONFIGURATION)
+ .logData(
+ Severity.ERROR,
+ "Analog input %s not found in config file, using mock analog input instead",
+ configName);
+ return new MockAnalogInput();
+ }
+ }
+
+ public RelayOutput getRelay(final String configName) {
+ try {
+ ValueProvider port =
+ ConfigFileReader.getInstance().getInt(configName + ".port");
+ checkDeviceName("relay", relayNames, port.get(), configName);
+
+ return getRelay(port.get());
+ } catch (IllegalArgumentException ex) {
+ Logger.get(Category.CONFIGURATION)
+ .logData(
+ Severity.ERROR,
+ "Relay %s not found in config file, using mock relay instead",
+ configName);
+ return new MockRelay(0);
+ }
+ }
+
+ public DoubleSolenoid getSolenoid(final String configName) {
+ try {
+ final String legacyConfigKey = configName + ".port";
+ ValueProvider forwardPorts =
+ ConfigFileReader.getInstance().containsKey(legacyConfigKey)
+ ? ConfigFileReader.getInstance().getInts(legacyConfigKey)
+ : ConfigFileReader.getInstance().getInts(configName + ".forwardPort");
+ ValueProvider reversePorts =
+ ConfigFileReader.getInstance().getInts(configName + ".reversePort");
+
+ for (Integer port : forwardPorts.valueOr(new Integer[0])) {
+ checkDeviceName("solenoid", solenoidNames, port, configName);
+ }
+ for (Integer port : reversePorts.valueOr(new Integer[0])) {
+ checkDeviceName("solenoid", solenoidNames, port, configName);
+ }
+
+ SolenoidController forwardSolenoids =
+ new MultiSolenoid(
+ Arrays.stream(forwardPorts.valueOr(new Integer[0]))
+ .map(this::getSolenoid)
+ .toArray(SolenoidController[]::new));
+ SolenoidController reverseSolenoids =
+ new MultiSolenoid(
+ Arrays.stream(reversePorts.valueOr(new Integer[0]))
+ .map(this::getSolenoid)
+ .toArray(SolenoidController[]::new));
+ return new DoubleSolenoid(forwardSolenoids, reverseSolenoids);
+ } catch (IllegalArgumentException ex) {
+ Logger.get(Category.CONFIGURATION)
+ .logData(
+ Severity.ERROR,
+ "Solenoid %s not found in config file, using mock solenoid instead %s",
+ configName,
+ ex.toString());
+ return new DoubleSolenoid(null, null);
+ }
+ }
+
+ public GyroReader getGyro(final String configName) {
+ try {
+ ValueProvider port =
+ ConfigFileReader.getInstance().getInt(configName + ".port");
+ checkDeviceName("gyro", gyroNames, port.get(), configName);
+
+ return getGyro(port.get());
+ } catch (IllegalArgumentException ex) {
+ Logger.get(Category.CONFIGURATION)
+ .logData(
+ Severity.ERROR,
+ "Gyro %s not found in config file, using mock gyro instead",
+ configName);
+ return new MockGyro();
+ }
+ }
+
+ // Operator Devices
+ public abstract JoystickReader getJoystick(int index);
+
+ public abstract CameraInterface getCamServer();
+
+ public abstract Clock getClock();
+
+ public abstract double getBatteryVoltage();
+
+ public abstract void refreshDriverStationData();
+
+ public abstract boolean hasNewDriverStationData();
}
diff --git a/src/main/java/com/team766/hal/simulator/RobotMain.java b/src/main/java/com/team766/hal/simulator/RobotMain.java
index cae35009..eda13057 100755
--- a/src/main/java/com/team766/hal/simulator/RobotMain.java
+++ b/src/main/java/com/team766/hal/simulator/RobotMain.java
@@ -1,127 +1,132 @@
package com.team766.hal.simulator;
-import java.io.IOException;
import com.team766.config.ConfigFileReader;
+import com.team766.framework.Scheduler;
import com.team766.hal.GenericRobotMain;
import com.team766.hal.RobotProvider;
import com.team766.logging.LoggerExceptionUtils;
import com.team766.simulator.Program;
import com.team766.simulator.ProgramInterface;
import com.team766.simulator.Simulator;
+import java.io.IOException;
public class RobotMain {
- enum Mode {
- MaroonSim,
- VrConnector,
- }
-
- private GenericRobotMain robot;
- private Runnable simulator;
+ enum Mode {
+ MaroonSim,
+ VrConnector,
+ }
- public RobotMain(final Mode mode) {
- try {
- // TODO: update this to come from deploy directory?
- ConfigFileReader.instance = new ConfigFileReader("simConfig.txt");
- RobotProvider.instance = new SimulationRobotProvider();
+ private GenericRobotMain robot;
+ private Runnable simulator;
- Scheduler.getInstance().reset();
+ public RobotMain(final Mode mode) {
+ try {
+ // TODO: update this to come from deploy directory?
+ ConfigFileReader.instance = new ConfigFileReader("simConfig.txt");
+ RobotProvider.instance = new SimulationRobotProvider();
- robot = new GenericRobotMain();
+ Scheduler.getInstance().reset();
- robot.robotInit();
+ robot = new GenericRobotMain();
- ProgramInterface.program = new Program() {
- ProgramInterface.RobotMode prevRobotMode = null;
+ robot.robotInit();
- @Override
- public void step(double dt) {
- switch (ProgramInterface.robotMode) {
- case DISABLED:
- if (prevRobotMode != ProgramInterface.RobotMode.DISABLED) {
- robot.disabledInit();
- prevRobotMode = ProgramInterface.RobotMode.DISABLED;
- }
- robot.disabledPeriodic();
- break;
- case AUTON:
- if (prevRobotMode != ProgramInterface.RobotMode.AUTON) {
- robot.autonomousInit();
- prevRobotMode = ProgramInterface.RobotMode.AUTON;
- }
- robot.autonomousPeriodic();
- break;
- case TELEOP:
- if (prevRobotMode != ProgramInterface.RobotMode.TELEOP) {
- robot.teleopInit();
- prevRobotMode = ProgramInterface.RobotMode.TELEOP;
- }
- robot.teleopPeriodic();
- break;
- default:
- LoggerExceptionUtils.logException(new IllegalArgumentException("Value of ProgramInterface.robotMode invalid. Provided value: " + ProgramInterface.robotMode));
- break;
- }
- }
+ ProgramInterface.program =
+ new Program() {
+ ProgramInterface.RobotMode prevRobotMode = null;
- @Override
- public void reset() {
- robot.resetAutonomousMode("simulation reset");
- }
- };
- } catch (Exception exc) {
- exc.printStackTrace();
- LoggerExceptionUtils.logException(exc);
- }
+ @Override
+ public void step(double dt) {
+ switch (ProgramInterface.robotMode) {
+ case DISABLED:
+ if (prevRobotMode != ProgramInterface.RobotMode.DISABLED) {
+ robot.disabledInit();
+ prevRobotMode = ProgramInterface.RobotMode.DISABLED;
+ }
+ robot.disabledPeriodic();
+ break;
+ case AUTON:
+ if (prevRobotMode != ProgramInterface.RobotMode.AUTON) {
+ robot.autonomousInit();
+ prevRobotMode = ProgramInterface.RobotMode.AUTON;
+ }
+ robot.autonomousPeriodic();
+ break;
+ case TELEOP:
+ if (prevRobotMode != ProgramInterface.RobotMode.TELEOP) {
+ robot.teleopInit();
+ prevRobotMode = ProgramInterface.RobotMode.TELEOP;
+ }
+ robot.teleopPeriodic();
+ break;
+ default:
+ LoggerExceptionUtils.logException(
+ new IllegalArgumentException(
+ "Value of ProgramInterface.robotMode invalid. Provided value: "
+ + ProgramInterface.robotMode));
+ break;
+ }
+ }
- switch (mode) {
- case MaroonSim:
- simulator = new Simulator();
- break;
- case VrConnector:
- ProgramInterface.robotMode = ProgramInterface.RobotMode.DISABLED;
- try {
- simulator = new VrConnector();
- } catch (IOException ex) {
- throw new RuntimeException("Error initializing communication with 3d Simulator",
- ex);
- }
- break;
- default:
- LoggerExceptionUtils.logException(
- new IllegalArgumentException("Unknown simulator mode. ProgramInterface.robotMode: " + ProgramInterface.robotMode)
- );
- break;
- }
+ @Override
+ public void reset() {
+ robot.resetAutonomousMode("simulation reset");
+ }
+ };
+ } catch (Exception exc) {
+ exc.printStackTrace();
+ LoggerExceptionUtils.logException(exc);
+ }
- }
+ switch (mode) {
+ case MaroonSim:
+ simulator = new Simulator();
+ break;
+ case VrConnector:
+ ProgramInterface.robotMode = ProgramInterface.RobotMode.DISABLED;
+ try {
+ simulator = new VrConnector();
+ } catch (IOException ex) {
+ throw new RuntimeException(
+ "Error initializing communication with 3d Simulator", ex);
+ }
+ break;
+ default:
+ LoggerExceptionUtils.logException(
+ new IllegalArgumentException(
+ "Unknown simulator mode. ProgramInterface.robotMode: "
+ + ProgramInterface.robotMode));
+ break;
+ }
+ }
- public void run() {
- try {
- simulator.run();
- } catch (Exception exc) {
- exc.printStackTrace();
- LoggerExceptionUtils.logException(exc);
- }
- }
+ public void run() {
+ try {
+ simulator.run();
+ } catch (Exception exc) {
+ exc.printStackTrace();
+ LoggerExceptionUtils.logException(exc);
+ }
+ }
- public static void main(final String[] args) {
- if (args.length != 1) {
- System.err.println("Needs -maroon_sim or -vr_connector");
- System.exit(1);
- }
- Mode mode;
- switch (args[0]) {
- case "-maroon_sim":
- mode = Mode.MaroonSim;
- break;
- case "-vr_connector":
- mode = Mode.VrConnector;
- break;
- default:
- System.err.println("Needs -maroon_sim or -vr_connector");
- System.exit(1);
- return;
- }
- new RobotMain(mode).run();
- }
+ public static void main(final String[] args) {
+ if (args.length != 1) {
+ System.err.println("Needs -maroon_sim or -vr_connector");
+ System.exit(1);
+ }
+ Mode mode;
+ switch (args[0]) {
+ case "-maroon_sim":
+ mode = Mode.MaroonSim;
+ break;
+ case "-vr_connector":
+ mode = Mode.VrConnector;
+ break;
+ default:
+ System.err.println("Needs -maroon_sim or -vr_connector");
+ System.exit(1);
+ return;
+ }
+ new RobotMain(mode).run();
+ }
}
diff --git a/src/main/java/com/team766/hal/simulator/VrConnector.java b/src/main/java/com/team766/hal/simulator/VrConnector.java
index 7cd8c3d5..e9ba551f 100644
--- a/src/main/java/com/team766/hal/simulator/VrConnector.java
+++ b/src/main/java/com/team766/hal/simulator/VrConnector.java
@@ -2,6 +2,8 @@
import static com.team766.math.Math.normalizeAngleDegrees;
+import com.team766.logging.LoggerExceptionUtils;
+import com.team766.simulator.ProgramInterface;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -13,355 +15,388 @@
import java.util.Arrays;
import java.util.List;
import java.util.Map;
-import com.team766.logging.LoggerExceptionUtils;
-import com.team766.simulator.ProgramInterface;
public class VrConnector implements Runnable {
- private static class PortMapping {
- public final int messageDataIndex;
- public final int robotPortIndex;
-
- PortMapping(final int messageIndex, final int robotIndex) {
- this.messageDataIndex = messageIndex;
- this.robotPortIndex = robotIndex;
- }
- }
-
- private static class CANPortMapping {
- public final int canId;
- public final int motorCommandMessageDataIndex;
- public final int sensorFeedbackMessageDataIndex;
-
- CANPortMapping(final int canId_, final int motorCommandMessageDataIndex_,
- final int sensorFeedbackMessageDataIndex_) {
- this.canId = canId_;
- this.motorCommandMessageDataIndex = motorCommandMessageDataIndex_;
- this.sensorFeedbackMessageDataIndex = sensorFeedbackMessageDataIndex_;
- }
- }
-
- /// Command indexes
-
- private static final int MAX_COMMANDS = 100;
-
- private static final int RESET_SIM_CHANNEL = 0;
-
- private static final List PWM_CHANNELS = Arrays.asList(
- // new PortMapping(10, 6), // Left motor
- // new PortMapping(11, 4), // Right motor
- // new PortMapping(14, 1), // Auxiliary / Center motor
- // new PortMapping(12, 0) // Intake
- );
-
- //CHECKSTYLE:OFF
- private static final List SOLENOID_CHANNELS = Arrays.asList(
- new PortMapping(15, 0), // Intake arm
- new PortMapping(13, 1) // Catapult launch
- );
- //CHECKSTYLE:ON
-
- private static final List RELAY_CHANNELS = Arrays.asList();
-
- //CHECKSTYLE:OFF
- private static final List CAN_MOTOR_CHANNELS = Arrays.asList(
- new CANPortMapping(6, 10, 10), // Left motor
- new CANPortMapping(4, 11, 11), // Right motor
- new CANPortMapping(10, 12, 13), // Intake
- new CANPortMapping(12, 14, 0), // Aux/center motor
- new CANPortMapping(14, 16, 0), // Aux2 motor
-
- new CANPortMapping(84, 84, 84), // FLD motor
- new CANPortMapping(85, 85, 85), // BLD motor
- new CANPortMapping(86, 86, 86), // FRD motor
- new CANPortMapping(87, 87, 87), // BRD motor
- new CANPortMapping(88, 88, 88), // FLS motor
- new CANPortMapping(89, 89, 89), // BLS motor
- new CANPortMapping(90, 90, 90), // FRS motor
- new CANPortMapping(91, 91, 91) // BRS motor
- );
- //CHECKSTYLE:ON
-
- /// Feedback indexes
-
- private static final int TIMESTAMP_LSW_CHANNEL = 5;
- private static final int TIMESTAMP_MSW_CHANNEL = 4;
-
- private static final int RESET_COUNTER_CHANNEL = 6;
-
- private static final int ROBOT_MODE_CHANNEL = 3;
- private static final Map ROBOT_MODES =
- Map.of(0, ProgramInterface.RobotMode.DISABLED, 1, ProgramInterface.RobotMode.AUTON, 2,
- ProgramInterface.RobotMode.TELEOP);
-
- private static final int ROBOT_X_CHANNEL = 8;
- private static final int ROBOT_Y_CHANNEL = 9;
-
- private static final int BEACON_SENSOR_START = 120;
- private static final int BEACON_SENSOR_STRIDE = 6; // (x, y, z, yaw, pitch, roll)
-
- //CHECKSTYLE:OFF
- private static final List ENCODER_CHANNELS = Arrays.asList(
- new PortMapping(10, 0), // Left encoder
- new PortMapping(11, 2), // Right encoder
- new PortMapping(13, 4) // Mechanism encoder
- );
- //CHECKSTYLE:ON
-
- private static final int GYRO_CHANNEL = 15;
- private static final int GYRO_RATE_CHANNEL = 16;
- private static final int GYRO_PITCH_CHANNEL = 80;
- private static final int GYRO_ROLL_CHANNEL = 81;
-
- //CHECKSTYLE:OFF
- private static final List DIGITAL_CHANNELS = Arrays.asList(
- new PortMapping(13, 0), // Intake state
- new PortMapping(14, 1), // Ball presence
- new PortMapping(17, 4), // Line Sensor 1
- new PortMapping(18, 5), // Line Sensor 2
- new PortMapping(19, 6) // Line Sensor 3
- );
- //CHECKSTYLE:ON
-
- private static final List ANALOG_CHANNELS = Arrays.asList();
-
- private static final int NUM_JOYSTICK = 4;
- private static final int BASE_AXIS_START = 20;
- private static final int BASE_AXES_PER_JOYSTICK = 4;
- private static final int ADDITIONAL_AXIS_START = 100;
- private static final int ADDITIONAL_AXES_PER_JOYSTICK = 4;
- private static final int JOYSTICK_BUTTON_START = 72;
- private static final int BUTTONS_PER_JOYSTICK = 20;
-
- /// Socket Communication
-
- private static final int commandsPort = 7661;
- private static final int feedbackPort = 7662;
- private static final int BUF_SZ = 1024;
-
- private long startTime;
- private boolean started = false;
-
- private Selector selector;
- private InetSocketAddress sendAddr;
- private ByteBuffer feedback = ByteBuffer.allocate(BUF_SZ);
- private ByteBuffer commands = ByteBuffer.allocate(BUF_SZ);
- private int resetCounter = 0;
-
- private int lastResetCounter = 0;
- private double lastGyroValue = Double.NaN;
- private long[] lastEncoderValue = new long[ProgramInterface.encoderChannels.length];
- private long[] lastCANSensorValue =
- new long[ProgramInterface.canMotorControllerChannels.length];
-
- private int getFeedback(final int index) {
- return feedback.getInt(index * 4);
- }
-
- private static long assembleLong(final int msw, final int lsw) {
- return ((long) msw << 32) | (lsw & 0xffffffffL);
- }
-
- private void putCommand(final int index, final int value) {
- commands.putInt(index * 4, value);
- }
-
- private void putCommandFloat(final int index, final double value) {
- putCommand(index, (int) (value * 512.0));
- }
-
- private void putCommandTristate(final int index, final int value) {
- if (value == 0) {
- putCommand(index, 0);
- } else if (value > 0) {
- putCommand(index, 511);
- } else {
- putCommand(index, -512);
- }
- }
-
- private void putCommandBool(final int index, final boolean value) {
- putCommand(index, value ? 511 : -512);
- }
-
- public VrConnector() throws IOException {
- selector = Selector.open();
- DatagramChannel channel = DatagramChannel.open();
- InetSocketAddress receiveAddr = new InetSocketAddress(feedbackPort);
- channel.bind(receiveAddr);
- sendAddr = new InetSocketAddress(InetAddress.getLoopbackAddress(), commandsPort);
- channel.configureBlocking(false);
- channel.register(selector, SelectionKey.OP_READ);
- commands.limit(MAX_COMMANDS * 4);
- commands.order(ByteOrder.LITTLE_ENDIAN);
- feedback.order(ByteOrder.LITTLE_ENDIAN);
- startTime = System.currentTimeMillis();
- }
-
- private boolean process() throws IOException {
- for (PortMapping m : PWM_CHANNELS) {
- putCommandFloat(m.messageDataIndex, ProgramInterface.pwmChannels[m.robotPortIndex]);
- }
- for (PortMapping m : SOLENOID_CHANNELS) {
- putCommandBool(m.messageDataIndex, ProgramInterface.solenoidChannels[m.robotPortIndex]);
- }
- for (PortMapping m : RELAY_CHANNELS) {
- putCommandTristate(m.messageDataIndex, ProgramInterface.relayChannels[m.robotPortIndex]);
- }
- for (CANPortMapping m : CAN_MOTOR_CHANNELS) {
- putCommandFloat(
- m.motorCommandMessageDataIndex,
- ProgramInterface.canMotorControllerChannels[m.canId].command.output);
- }
-
- selector.selectedKeys().clear();
- selector.select();
- boolean newData = false;
- for (SelectionKey key : selector.selectedKeys()) {
- if (!key.isValid()) {
- continue;
- }
-
- DatagramChannel chan = (DatagramChannel) key.channel();
- if (key.isReadable()) {
- feedback.clear();
- chan.receive(feedback);
- newData = true;
- key.interestOps(SelectionKey.OP_WRITE);
- }
- if (key.isWritable()) {
- if (started) {
- chan.send(commands.duplicate(), sendAddr);
- putCommand(RESET_SIM_CHANNEL, 0);
- }
- key.interestOps(SelectionKey.OP_READ);
- }
- }
-
- if (newData) {
- double prevSimTime = ProgramInterface.simulationTime;
- // Time is sent in milliseconds
- ProgramInterface.simulationTime = assembleLong(
- getFeedback(TIMESTAMP_MSW_CHANNEL), getFeedback(TIMESTAMP_LSW_CHANNEL)) * 0.001;
-
- resetCounter = getFeedback(RESET_COUNTER_CHANNEL);
-
- ProgramInterface.robotMode = ROBOT_MODES.get(getFeedback(ROBOT_MODE_CHANNEL));
-
- final double gyroValue = getFeedback(GYRO_CHANNEL) / 10.0;
- if (Double.isNaN(lastGyroValue)) {
- lastGyroValue = gyroValue;
- }
- ProgramInterface.gyro.angle += gyroValue - lastGyroValue;
- lastGyroValue = gyroValue;
-
- ProgramInterface.robotPosition.x = getFeedback(ROBOT_X_CHANNEL) / 1000.0;
- ProgramInterface.robotPosition.y = getFeedback(ROBOT_Y_CHANNEL) / 1000.0;
- ProgramInterface.robotPosition.heading = gyroValue;
-
- ProgramInterface.gyro.rate = getFeedback(GYRO_RATE_CHANNEL) / 100.0;
- ProgramInterface.gyro.pitch = normalizeAngleDegrees(getFeedback(GYRO_PITCH_CHANNEL) / 10.0);
- ProgramInterface.gyro.roll = normalizeAngleDegrees(getFeedback(GYRO_ROLL_CHANNEL) / 10.0);
-
- for (int i = 0; i < ProgramInterface.NUM_BEACONS; ++i) {
- ProgramInterface.beacons[i].x = getFeedback(BEACON_SENSOR_START + i * BEACON_SENSOR_STRIDE + 0) / 1000.0;
- ProgramInterface.beacons[i].y = getFeedback(BEACON_SENSOR_START + i * BEACON_SENSOR_STRIDE + 1) / 1000.0;
- ProgramInterface.beacons[i].z = getFeedback(BEACON_SENSOR_START + i * BEACON_SENSOR_STRIDE + 2) / 1000.0;
- ProgramInterface.beacons[i].yaw = getFeedback(BEACON_SENSOR_START + i * BEACON_SENSOR_STRIDE + 3) / 1000.0;
- ProgramInterface.beacons[i].pitch = getFeedback(BEACON_SENSOR_START + i * BEACON_SENSOR_STRIDE + 4) / 1000.0;
- ProgramInterface.beacons[i].roll = getFeedback(BEACON_SENSOR_START + i * BEACON_SENSOR_STRIDE + 5) / 1000.0;
- }
-
- for (PortMapping m : ENCODER_CHANNELS) {
- final long value = getFeedback(m.messageDataIndex);
- final long delta = value - lastEncoderValue[m.robotPortIndex];
- lastEncoderValue[m.robotPortIndex] = value;
-
- ProgramInterface.encoderChannels[m.robotPortIndex].distance += delta;
- if (ProgramInterface.simulationTime > prevSimTime) {
- ProgramInterface.encoderChannels[m.robotPortIndex].rate = delta / (ProgramInterface.simulationTime - prevSimTime);
- }
- }
- for (CANPortMapping m : CAN_MOTOR_CHANNELS) {
- var status = ProgramInterface.canMotorControllerChannels[m.canId].status;
-
- long value = getFeedback(m.sensorFeedbackMessageDataIndex);
- long delta = value - lastCANSensorValue[m.canId];
- lastCANSensorValue[m.canId] = value;
-
- status.sensorPosition += delta;
- if (ProgramInterface.simulationTime > prevSimTime) {
- status.sensorVelocity = delta / (ProgramInterface.simulationTime - prevSimTime);
- }
- }
- for (PortMapping m : DIGITAL_CHANNELS) {
- ProgramInterface.digitalChannels[m.robotPortIndex] = getFeedback(m.messageDataIndex) > 0;
- }
- for (PortMapping m : ANALOG_CHANNELS) {
- ProgramInterface.analogChannels[m.robotPortIndex] = getFeedback(m.messageDataIndex) * 5.0 / 1024.0;
- }
- for (int j = 0; j < NUM_JOYSTICK; ++j) {
- for (int a = 0; a < BASE_AXES_PER_JOYSTICK; ++a) {
- ProgramInterface.joystickChannels[j].setAxisValue(a, getFeedback(j * BASE_AXES_PER_JOYSTICK + a + BASE_AXIS_START) / 100.0);
- }
- for (int a = 0; a < ADDITIONAL_AXES_PER_JOYSTICK; ++a) {
- ProgramInterface.joystickChannels[j].setAxisValue(a + BASE_AXES_PER_JOYSTICK, getFeedback(j * ADDITIONAL_AXES_PER_JOYSTICK + a + ADDITIONAL_AXIS_START) / 100.0);
- }
- int denseButtonState = getFeedback(j + JOYSTICK_BUTTON_START);
- for (int b = 0; b < BUTTONS_PER_JOYSTICK; ++b) {
- ProgramInterface.joystickChannels[j].setButton(b + 1, ((denseButtonState >> b) & 1) != 0);
- }
- }
-
- ++ProgramInterface.driverStationUpdateNumber;
- }
-
- return newData;
- }
-
- public void run() {
- double prevSimTime = 0;
- while (true) {
- boolean newData = false;
- try {
- newData = process();
- } catch (Exception e) {
- e.printStackTrace();
- LoggerExceptionUtils.logException(e);
- try {
- Thread.sleep(10);
- } catch (InterruptedException e1) {
- }
- }
- if (ProgramInterface.simulationTime == 0) {
- // Wait for a connection to the simulator before starting to run the robot code.
- startTime = System.currentTimeMillis();
- continue;
- }
- if (resetCounter != lastResetCounter) {
- lastResetCounter = resetCounter;
- ProgramInterface.program.reset();
- }
- if (!newData) {
- continue;
- }
- if (!started) {
- // When the simulator has already been running, we seem to need to allow the socket
- // service loop to run for a bit to allow buffers in the sockets that communicate
- // with the simulator to flush, otherwise the messages queue up which results in a
- // significant control latency.
- if (System.currentTimeMillis() - startTime > 1000) {
- System.out.println("Starting simulation");
- started = true;
- } else {
- continue;
- }
- prevSimTime = ProgramInterface.simulationTime;
- }
- if (ProgramInterface.program != null) {
- final double time = ProgramInterface.simulationTime;
- ProgramInterface.program.step(time - prevSimTime);
- prevSimTime = time;
- }
- }
- }
+ private static class PortMapping {
+ public final int messageDataIndex;
+ public final int robotPortIndex;
+
+ PortMapping(final int messageIndex, final int robotIndex) {
+ this.messageDataIndex = messageIndex;
+ this.robotPortIndex = robotIndex;
+ }
+ }
+
+ private static class CANPortMapping {
+ public final int canId;
+ public final int motorCommandMessageDataIndex;
+ public final int sensorFeedbackMessageDataIndex;
+
+ CANPortMapping(
+ final int canId_,
+ final int motorCommandMessageDataIndex_,
+ final int sensorFeedbackMessageDataIndex_) {
+ this.canId = canId_;
+ this.motorCommandMessageDataIndex = motorCommandMessageDataIndex_;
+ this.sensorFeedbackMessageDataIndex = sensorFeedbackMessageDataIndex_;
+ }
+ }
+
+ /// Command indexes
+
+ private static final int MAX_COMMANDS = 100;
+
+ private static final int RESET_SIM_CHANNEL = 0;
+
+ private static final List PWM_CHANNELS =
+ Arrays.asList(
+ // new PortMapping(10, 6), // Left motor
+ // new PortMapping(11, 4), // Right motor
+ // new PortMapping(14, 1), // Auxiliary / Center motor
+ // new PortMapping(12, 0) // Intake
+ );
+
+ // CHECKSTYLE:OFF
+ private static final List SOLENOID_CHANNELS =
+ Arrays.asList(
+ new PortMapping(15, 0), // Intake arm
+ new PortMapping(13, 1) // Catapult launch
+ );
+ // CHECKSTYLE:ON
+
+ private static final List RELAY_CHANNELS = Arrays.asList();
+
+ // CHECKSTYLE:OFF
+ private static final List CAN_MOTOR_CHANNELS =
+ Arrays.asList(
+ new CANPortMapping(6, 10, 10), // Left motor
+ new CANPortMapping(4, 11, 11), // Right motor
+ new CANPortMapping(10, 12, 13), // Intake
+ new CANPortMapping(12, 14, 0), // Aux/center motor
+ new CANPortMapping(14, 16, 0), // Aux2 motor
+ new CANPortMapping(84, 84, 84), // FLD motor
+ new CANPortMapping(85, 85, 85), // BLD motor
+ new CANPortMapping(86, 86, 86), // FRD motor
+ new CANPortMapping(87, 87, 87), // BRD motor
+ new CANPortMapping(88, 88, 88), // FLS motor
+ new CANPortMapping(89, 89, 89), // BLS motor
+ new CANPortMapping(90, 90, 90), // FRS motor
+ new CANPortMapping(91, 91, 91) // BRS motor
+ );
+ // CHECKSTYLE:ON
+
+ /// Feedback indexes
+
+ private static final int TIMESTAMP_LSW_CHANNEL = 5;
+ private static final int TIMESTAMP_MSW_CHANNEL = 4;
+
+ private static final int RESET_COUNTER_CHANNEL = 6;
+
+ private static final int ROBOT_MODE_CHANNEL = 3;
+ private static final Map ROBOT_MODES =
+ Map.of(
+ 0,
+ ProgramInterface.RobotMode.DISABLED,
+ 1,
+ ProgramInterface.RobotMode.AUTON,
+ 2,
+ ProgramInterface.RobotMode.TELEOP);
+
+ private static final int ROBOT_X_CHANNEL = 8;
+ private static final int ROBOT_Y_CHANNEL = 9;
+
+ private static final int BEACON_SENSOR_START = 120;
+ private static final int BEACON_SENSOR_STRIDE = 6; // (x, y, z, yaw, pitch, roll)
+
+ // CHECKSTYLE:OFF
+ private static final List ENCODER_CHANNELS =
+ Arrays.asList(
+ new PortMapping(10, 0), // Left encoder
+ new PortMapping(11, 2), // Right encoder
+ new PortMapping(13, 4) // Mechanism encoder
+ );
+ // CHECKSTYLE:ON
+
+ private static final int GYRO_CHANNEL = 15;
+ private static final int GYRO_RATE_CHANNEL = 16;
+ private static final int GYRO_PITCH_CHANNEL = 80;
+ private static final int GYRO_ROLL_CHANNEL = 81;
+
+ // CHECKSTYLE:OFF
+ private static final List DIGITAL_CHANNELS =
+ Arrays.asList(
+ new PortMapping(13, 0), // Intake state
+ new PortMapping(14, 1), // Ball presence
+ new PortMapping(17, 4), // Line Sensor 1
+ new PortMapping(18, 5), // Line Sensor 2
+ new PortMapping(19, 6) // Line Sensor 3
+ );
+ // CHECKSTYLE:ON
+
+ private static final List ANALOG_CHANNELS = Arrays.asList();
+
+ private static final int NUM_JOYSTICK = 4;
+ private static final int BASE_AXIS_START = 20;
+ private static final int BASE_AXES_PER_JOYSTICK = 4;
+ private static final int ADDITIONAL_AXIS_START = 100;
+ private static final int ADDITIONAL_AXES_PER_JOYSTICK = 4;
+ private static final int JOYSTICK_BUTTON_START = 72;
+ private static final int BUTTONS_PER_JOYSTICK = 20;
+
+ /// Socket Communication
+
+ private static final int commandsPort = 7661;
+ private static final int feedbackPort = 7662;
+ private static final int BUF_SZ = 1024;
+
+ private long startTime;
+ private boolean started = false;
+
+ private Selector selector;
+ private InetSocketAddress sendAddr;
+ private ByteBuffer feedback = ByteBuffer.allocate(BUF_SZ);
+ private ByteBuffer commands = ByteBuffer.allocate(BUF_SZ);
+ private int resetCounter = 0;
+
+ private int lastResetCounter = 0;
+ private double lastGyroValue = Double.NaN;
+ private long[] lastEncoderValue = new long[ProgramInterface.encoderChannels.length];
+ private long[] lastCANSensorValue =
+ new long[ProgramInterface.canMotorControllerChannels.length];
+
+ private int getFeedback(final int index) {
+ return feedback.getInt(index * 4);
+ }
+
+ private static long assembleLong(final int msw, final int lsw) {
+ return ((long) msw << 32) | (lsw & 0xffffffffL);
+ }
+
+ private void putCommand(final int index, final int value) {
+ commands.putInt(index * 4, value);
+ }
+
+ private void putCommandFloat(final int index, final double value) {
+ putCommand(index, (int) (value * 512.0));
+ }
+
+ private void putCommandTristate(final int index, final int value) {
+ if (value == 0) {
+ putCommand(index, 0);
+ } else if (value > 0) {
+ putCommand(index, 511);
+ } else {
+ putCommand(index, -512);
+ }
+ }
+
+ private void putCommandBool(final int index, final boolean value) {
+ putCommand(index, value ? 511 : -512);
+ }
+
+ public VrConnector() throws IOException {
+ selector = Selector.open();
+ DatagramChannel channel = DatagramChannel.open();
+ InetSocketAddress receiveAddr = new InetSocketAddress(feedbackPort);
+ channel.bind(receiveAddr);
+ sendAddr = new InetSocketAddress(InetAddress.getLoopbackAddress(), commandsPort);
+ channel.configureBlocking(false);
+ channel.register(selector, SelectionKey.OP_READ);
+ commands.limit(MAX_COMMANDS * 4);
+ commands.order(ByteOrder.LITTLE_ENDIAN);
+ feedback.order(ByteOrder.LITTLE_ENDIAN);
+ startTime = System.currentTimeMillis();
+ }
+
+ private boolean process() throws IOException {
+ for (PortMapping m : PWM_CHANNELS) {
+ putCommandFloat(m.messageDataIndex, ProgramInterface.pwmChannels[m.robotPortIndex]);
+ }
+ for (PortMapping m : SOLENOID_CHANNELS) {
+ putCommandBool(m.messageDataIndex, ProgramInterface.solenoidChannels[m.robotPortIndex]);
+ }
+ for (PortMapping m : RELAY_CHANNELS) {
+ putCommandTristate(
+ m.messageDataIndex, ProgramInterface.relayChannels[m.robotPortIndex]);
+ }
+ for (CANPortMapping m : CAN_MOTOR_CHANNELS) {
+ putCommandFloat(
+ m.motorCommandMessageDataIndex,
+ ProgramInterface.canMotorControllerChannels[m.canId].command.output);
+ }
+
+ selector.selectedKeys().clear();
+ selector.select();
+ boolean newData = false;
+ for (SelectionKey key : selector.selectedKeys()) {
+ if (!key.isValid()) {
+ continue;
+ }
+
+ DatagramChannel chan = (DatagramChannel) key.channel();
+ if (key.isReadable()) {
+ feedback.clear();
+ chan.receive(feedback);
+ newData = true;
+ key.interestOps(SelectionKey.OP_WRITE);
+ }
+ if (key.isWritable()) {
+ if (started) {
+ chan.send(commands.duplicate(), sendAddr);
+ putCommand(RESET_SIM_CHANNEL, 0);
+ }
+ key.interestOps(SelectionKey.OP_READ);
+ }
+ }
+
+ if (newData) {
+ double prevSimTime = ProgramInterface.simulationTime;
+ // Time is sent in milliseconds
+ ProgramInterface.simulationTime =
+ assembleLong(
+ getFeedback(TIMESTAMP_MSW_CHANNEL),
+ getFeedback(TIMESTAMP_LSW_CHANNEL))
+ * 0.001;
+
+ resetCounter = getFeedback(RESET_COUNTER_CHANNEL);
+
+ ProgramInterface.robotMode = ROBOT_MODES.get(getFeedback(ROBOT_MODE_CHANNEL));
+
+ final double gyroValue = getFeedback(GYRO_CHANNEL) / 10.0;
+ if (Double.isNaN(lastGyroValue)) {
+ lastGyroValue = gyroValue;
+ }
+ ProgramInterface.gyro.angle += gyroValue - lastGyroValue;
+ lastGyroValue = gyroValue;
+
+ ProgramInterface.robotPosition.x = getFeedback(ROBOT_X_CHANNEL) / 1000.0;
+ ProgramInterface.robotPosition.y = getFeedback(ROBOT_Y_CHANNEL) / 1000.0;
+ ProgramInterface.robotPosition.heading = gyroValue;
+
+ ProgramInterface.gyro.rate = getFeedback(GYRO_RATE_CHANNEL) / 100.0;
+ ProgramInterface.gyro.pitch =
+ normalizeAngleDegrees(getFeedback(GYRO_PITCH_CHANNEL) / 10.0);
+ ProgramInterface.gyro.roll =
+ normalizeAngleDegrees(getFeedback(GYRO_ROLL_CHANNEL) / 10.0);
+
+ for (int i = 0; i < ProgramInterface.NUM_BEACONS; ++i) {
+ ProgramInterface.beacons[i].x =
+ getFeedback(BEACON_SENSOR_START + i * BEACON_SENSOR_STRIDE + 0) / 1000.0;
+ ProgramInterface.beacons[i].y =
+ getFeedback(BEACON_SENSOR_START + i * BEACON_SENSOR_STRIDE + 1) / 1000.0;
+ ProgramInterface.beacons[i].z =
+ getFeedback(BEACON_SENSOR_START + i * BEACON_SENSOR_STRIDE + 2) / 1000.0;
+ ProgramInterface.beacons[i].yaw =
+ getFeedback(BEACON_SENSOR_START + i * BEACON_SENSOR_STRIDE + 3) / 1000.0;
+ ProgramInterface.beacons[i].pitch =
+ getFeedback(BEACON_SENSOR_START + i * BEACON_SENSOR_STRIDE + 4) / 1000.0;
+ ProgramInterface.beacons[i].roll =
+ getFeedback(BEACON_SENSOR_START + i * BEACON_SENSOR_STRIDE + 5) / 1000.0;
+ }
+
+ for (PortMapping m : ENCODER_CHANNELS) {
+ final long value = getFeedback(m.messageDataIndex);
+ final long delta = value - lastEncoderValue[m.robotPortIndex];
+ lastEncoderValue[m.robotPortIndex] = value;
+
+ ProgramInterface.encoderChannels[m.robotPortIndex].distance += delta;
+ if (ProgramInterface.simulationTime > prevSimTime) {
+ ProgramInterface.encoderChannels[m.robotPortIndex].rate =
+ delta / (ProgramInterface.simulationTime - prevSimTime);
+ }
+ }
+ for (CANPortMapping m : CAN_MOTOR_CHANNELS) {
+ var status = ProgramInterface.canMotorControllerChannels[m.canId].status;
+
+ long value = getFeedback(m.sensorFeedbackMessageDataIndex);
+ long delta = value - lastCANSensorValue[m.canId];
+ lastCANSensorValue[m.canId] = value;
+
+ status.sensorPosition += delta;
+ if (ProgramInterface.simulationTime > prevSimTime) {
+ status.sensorVelocity = delta / (ProgramInterface.simulationTime - prevSimTime);
+ }
+ }
+ for (PortMapping m : DIGITAL_CHANNELS) {
+ ProgramInterface.digitalChannels[m.robotPortIndex] =
+ getFeedback(m.messageDataIndex) > 0;
+ }
+ for (PortMapping m : ANALOG_CHANNELS) {
+ ProgramInterface.analogChannels[m.robotPortIndex] =
+ getFeedback(m.messageDataIndex) * 5.0 / 1024.0;
+ }
+ for (int j = 0; j < NUM_JOYSTICK; ++j) {
+ for (int a = 0; a < BASE_AXES_PER_JOYSTICK; ++a) {
+ ProgramInterface.joystickChannels[j].setAxisValue(
+ a,
+ getFeedback(j * BASE_AXES_PER_JOYSTICK + a + BASE_AXIS_START) / 100.0);
+ }
+ for (int a = 0; a < ADDITIONAL_AXES_PER_JOYSTICK; ++a) {
+ ProgramInterface.joystickChannels[j].setAxisValue(
+ a + BASE_AXES_PER_JOYSTICK,
+ getFeedback(
+ j * ADDITIONAL_AXES_PER_JOYSTICK
+ + a
+ + ADDITIONAL_AXIS_START)
+ / 100.0);
+ }
+ int denseButtonState = getFeedback(j + JOYSTICK_BUTTON_START);
+ for (int b = 0; b < BUTTONS_PER_JOYSTICK; ++b) {
+ ProgramInterface.joystickChannels[j].setButton(
+ b + 1, ((denseButtonState >> b) & 1) != 0);
+ }
+ }
+
+ ++ProgramInterface.driverStationUpdateNumber;
+ }
+
+ return newData;
+ }
+
+ public void run() {
+ double prevSimTime = 0;
+ while (true) {
+ boolean newData = false;
+ try {
+ newData = process();
+ } catch (Exception e) {
+ e.printStackTrace();
+ LoggerExceptionUtils.logException(e);
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e1) {
+ }
+ }
+ if (ProgramInterface.simulationTime == 0) {
+ // Wait for a connection to the simulator before starting to run the robot code.
+ startTime = System.currentTimeMillis();
+ continue;
+ }
+ if (resetCounter != lastResetCounter) {
+ lastResetCounter = resetCounter;
+ ProgramInterface.program.reset();
+ }
+ if (!newData) {
+ continue;
+ }
+ if (!started) {
+ // When the simulator has already been running, we seem to need to allow the socket
+ // service loop to run for a bit to allow buffers in the sockets that communicate
+ // with the simulator to flush, otherwise the messages queue up which results in a
+ // significant control latency.
+ if (System.currentTimeMillis() - startTime > 1000) {
+ System.out.println("Starting simulation");
+ started = true;
+ } else {
+ continue;
+ }
+ prevSimTime = ProgramInterface.simulationTime;
+ }
+ if (ProgramInterface.program != null) {
+ final double time = ProgramInterface.simulationTime;
+ ProgramInterface.program.step(time - prevSimTime);
+ prevSimTime = time;
+ }
+ }
+ }
}
diff --git a/src/main/java/com/team766/hal/wpilib/CANSparkMaxMotorController.java b/src/main/java/com/team766/hal/wpilib/CANSparkMaxMotorController.java
index 489c30c4..56b2b0a1 100644
--- a/src/main/java/com/team766/hal/wpilib/CANSparkMaxMotorController.java
+++ b/src/main/java/com/team766/hal/wpilib/CANSparkMaxMotorController.java
@@ -1,7 +1,5 @@
package com.team766.hal.wpilib;
-import java.util.function.Function;
-import java.util.function.Supplier;
import com.ctre.phoenix.motorcontrol.FeedbackDevice;
import com.ctre.phoenix.motorcontrol.NeutralMode;
import com.revrobotics.CANSparkMax;
@@ -11,240 +9,249 @@
import com.team766.hal.MotorController;
import com.team766.hal.MotorControllerCommandFailedException;
import com.team766.logging.LoggerExceptionUtils;
+import java.util.function.Function;
+import java.util.function.Supplier;
public class CANSparkMaxMotorController extends CANSparkMax implements MotorController {
- private Supplier sensorPositionSupplier;
- private Supplier sensorVelocitySupplier;
- private Function sensorPositionSetter;
- private Function sensorInvertedSetter;
- private boolean sensorInverted = false;
-
- public CANSparkMaxMotorController(final int deviceId) {
- super(deviceId, MotorType.kBrushless);
-
- // Set default feedback device. This ensures that our implementations of
- // getSensorPosition/getSensorVelocity return values that match what the
- // device's PID controller is using.
- setSelectedFeedbackSensor(FeedbackDevice.IntegratedSensor);
- }
-
- private enum ExceptionTarget {
- THROW,
- LOG,
- }
-
- private static void revErrorToException(final ExceptionTarget throwEx, final REVLibError err) {
- if (err == REVLibError.kOk) {
- return;
- }
- var ex = new MotorControllerCommandFailedException(err.toString());
- switch (throwEx) {
- case THROW:
- throw ex;
- default: case LOG:
- LoggerExceptionUtils.logException(ex);
- break;
- }
- }
-
- @Override
- public double getSensorPosition() {
- return sensorPositionSupplier.get();
- }
-
- @Override
- public double getSensorVelocity() {
- return sensorVelocitySupplier.get();
- }
-
- @Override
- public void set(final ControlMode mode, final double value) {
- switch (mode) {
- case Current:
- getPIDController().setReference(value, CANSparkMax.ControlType.kCurrent);
- break;
- case Disabled:
- disable();
- break;
- case Follower:
- throw new IllegalArgumentException("Use follow() method instead");
- case MotionMagic:
- throw new IllegalArgumentException("SparkMax does not support MotionMagic");
- case MotionProfile:
- throw new IllegalArgumentException("SparkMax does not support MotionProfile");
- case MotionProfileArc:
- throw new IllegalArgumentException("SparkMax does not support MotionProfileArc");
- case PercentOutput:
- getPIDController().setReference(value, CANSparkMax.ControlType.kDutyCycle);
- break;
- case Position:
- getPIDController().setReference(value, CANSparkMax.ControlType.kPosition);
- break;
- case Velocity:
- getPIDController().setReference(value, CANSparkMax.ControlType.kVelocity);
- break;
- case Voltage:
- getPIDController().setReference(value, CANSparkMax.ControlType.kVoltage);
- default:
- throw new IllegalArgumentException("Unsupported control mode " + mode);
- }
- }
-
- @Override
- public void setSensorPosition(final double position) {
- revErrorToException(ExceptionTarget.THROW, sensorPositionSetter.apply(position));
- }
-
- @Override
- public void follow(final MotorController leader) {
- try {
- revErrorToException(ExceptionTarget.LOG, super.follow((CANSparkMax) leader));
- } catch (ClassCastException ex) {
- LoggerExceptionUtils.logException(new IllegalArgumentException(
- "Spark Max can only follow another Spark Max", ex));
- }
- }
-
- @Override
- public void setNeutralMode(final NeutralMode neutralMode) {
- switch (neutralMode) {
- case Brake:
- revErrorToException(ExceptionTarget.LOG, setIdleMode(IdleMode.kBrake));
- break;
- case Coast:
- revErrorToException(ExceptionTarget.LOG, setIdleMode(IdleMode.kCoast));
- break;
- default:
- LoggerExceptionUtils.logException(new IllegalArgumentException("Unsupported neutral mode " + neutralMode));
- break;
- }
- }
-
- @Override
- public void setP(final double value) {
- revErrorToException(ExceptionTarget.LOG, getPIDController().setP(value));
- }
-
- @Override
- public void setI(final double value) {
- revErrorToException(ExceptionTarget.LOG, getPIDController().setI(value));
- }
-
- @Override
- public void setD(final double value) {
- revErrorToException(ExceptionTarget.LOG, getPIDController().setD(value));
- }
-
- @Override
- public void setFF(final double value) {
- revErrorToException(ExceptionTarget.LOG, getPIDController().setFF(value));
- }
-
- @Override
- public void setSelectedFeedbackSensor(final FeedbackDevice feedbackDevice) {
- switch (feedbackDevice) {
- case Analog: {
- SparkMaxAnalogSensor analog = getAnalog(SparkMaxAnalogSensor.Mode.kAbsolute);
- revErrorToException(ExceptionTarget.LOG, analog.setInverted(sensorInverted));
- sensorPositionSupplier = analog::getPosition;
- sensorVelocitySupplier = analog::getVelocity;
- sensorPositionSetter = (pos) -> REVLibError.kOk;
- sensorInvertedSetter = analog::setInverted;
- revErrorToException(ExceptionTarget.LOG,
- getPIDController().setFeedbackDevice(analog));
- return;
- }
- case CTRE_MagEncoder_Absolute:
- LoggerExceptionUtils.logException(
- new IllegalArgumentException("SparkMax does not support CTRE Mag Encoder"));
- case CTRE_MagEncoder_Relative:
- LoggerExceptionUtils.logException(
- new IllegalArgumentException("SparkMax does not support CTRE Mag Encoder"));
- case IntegratedSensor: {
- RelativeEncoder encoder = getEncoder();
- // NOTE(rcahoon, 2022-04-19): Don't call this. Trying to call setInverted on the
- // integrated sensor returns an error.
- // revErrorToException(ExceptionTarget.LOG, encoder.setInverted(sensorInverted));
- sensorPositionSupplier = encoder::getPosition;
- sensorVelocitySupplier = encoder::getVelocity;
- sensorPositionSetter = encoder::setPosition;
- // NOTE(rcahoon, 2022-04-19): Don't call this. Trying to call setInverted on the
- // integrated sensor returns an error.
- // sensorInvertedSetter = encoder::setInverted;
- sensorInvertedSetter = (inverted) -> REVLibError.kOk;
- revErrorToException(ExceptionTarget.LOG,
- getPIDController().setFeedbackDevice(encoder));
- return;
- }
- case None:
- return;
- case PulseWidthEncodedPosition:
- LoggerExceptionUtils.logException(
- new IllegalArgumentException("SparkMax does not support PWM sensors"));
- case QuadEncoder:
- // TODO: should we pass a real counts-per-rev scale here?
- RelativeEncoder encoder = getAlternateEncoder(1);
- revErrorToException(ExceptionTarget.LOG, encoder.setInverted(sensorInverted));
- sensorPositionSupplier = encoder::getPosition;
- sensorVelocitySupplier = encoder::getVelocity;
- sensorPositionSetter = encoder::setPosition;
- sensorInvertedSetter = encoder::setInverted;
- revErrorToException(ExceptionTarget.LOG,
- getPIDController().setFeedbackDevice(encoder));
- return;
- case RemoteSensor0:
- LoggerExceptionUtils.logException(
- new IllegalArgumentException("SparkMax does not support remote sensors"));
- case RemoteSensor1:
- LoggerExceptionUtils.logException(
- new IllegalArgumentException("SparkMax does not support remote sensors"));
- case SensorDifference:
- LoggerExceptionUtils.logException(
- new IllegalArgumentException("SparkMax does not support SensorDifference"));
- case SensorSum:
- LoggerExceptionUtils.logException(
- new IllegalArgumentException("SparkMax does not support SensorSum"));
- case SoftwareEmulatedSensor:
- LoggerExceptionUtils.logException(new IllegalArgumentException(
- "SparkMax does not support SoftwareEmulatedSensor"));
- case Tachometer:
- LoggerExceptionUtils.logException(
- new IllegalArgumentException("SparkMax does not support Tachometer"));
- default:
- LoggerExceptionUtils.logException(
- new IllegalArgumentException("Unsupported sensor type " + feedbackDevice));
- }
- }
-
- @Override
- public void setSensorInverted(final boolean inverted) {
- sensorInverted = inverted;
- revErrorToException(ExceptionTarget.LOG, sensorInvertedSetter.apply(inverted));
- }
-
- @Override
- public void setOutputRange(final double minOutput, final double maxOutput) {
- revErrorToException(ExceptionTarget.LOG,
- getPIDController().setOutputRange(minOutput, maxOutput));
- }
-
- public void setCurrentLimit(final double ampsLimit) {
- revErrorToException(ExceptionTarget.LOG, setSmartCurrentLimit((int) ampsLimit));
- }
-
- @Override
- public void restoreFactoryDefault() {
- revErrorToException(ExceptionTarget.LOG, restoreFactoryDefaults());
- }
-
- @Override
- public void setOpenLoopRamp(final double secondsFromNeutralToFull) {
- revErrorToException(ExceptionTarget.LOG, setOpenLoopRampRate(secondsFromNeutralToFull));
- }
-
- @Override
- public void setClosedLoopRamp(final double secondsFromNeutralToFull) {
- revErrorToException(ExceptionTarget.LOG, setClosedLoopRampRate(secondsFromNeutralToFull));
- }
+ private Supplier sensorPositionSupplier;
+ private Supplier sensorVelocitySupplier;
+ private Function sensorPositionSetter;
+ private Function sensorInvertedSetter;
+ private boolean sensorInverted = false;
+
+ public CANSparkMaxMotorController(final int deviceId) {
+ super(deviceId, MotorType.kBrushless);
+
+ // Set default feedback device. This ensures that our implementations of
+ // getSensorPosition/getSensorVelocity return values that match what the
+ // device's PID controller is using.
+ setSelectedFeedbackSensor(FeedbackDevice.IntegratedSensor);
+ }
+
+ private enum ExceptionTarget {
+ THROW,
+ LOG,
+ }
+
+ private static void revErrorToException(final ExceptionTarget throwEx, final REVLibError err) {
+ if (err == REVLibError.kOk) {
+ return;
+ }
+ var ex = new MotorControllerCommandFailedException(err.toString());
+ switch (throwEx) {
+ case THROW:
+ throw ex;
+ default:
+ case LOG:
+ LoggerExceptionUtils.logException(ex);
+ break;
+ }
+ }
+
+ @Override
+ public double getSensorPosition() {
+ return sensorPositionSupplier.get();
+ }
+
+ @Override
+ public double getSensorVelocity() {
+ return sensorVelocitySupplier.get();
+ }
+
+ @Override
+ public void set(final ControlMode mode, final double value) {
+ switch (mode) {
+ case Current:
+ getPIDController().setReference(value, CANSparkMax.ControlType.kCurrent);
+ break;
+ case Disabled:
+ disable();
+ break;
+ case Follower:
+ throw new IllegalArgumentException("Use follow() method instead");
+ case MotionMagic:
+ throw new IllegalArgumentException("SparkMax does not support MotionMagic");
+ case MotionProfile:
+ throw new IllegalArgumentException("SparkMax does not support MotionProfile");
+ case MotionProfileArc:
+ throw new IllegalArgumentException("SparkMax does not support MotionProfileArc");
+ case PercentOutput:
+ getPIDController().setReference(value, CANSparkMax.ControlType.kDutyCycle);
+ break;
+ case Position:
+ getPIDController().setReference(value, CANSparkMax.ControlType.kPosition);
+ break;
+ case Velocity:
+ getPIDController().setReference(value, CANSparkMax.ControlType.kVelocity);
+ break;
+ case Voltage:
+ getPIDController().setReference(value, CANSparkMax.ControlType.kVoltage);
+ default:
+ throw new IllegalArgumentException("Unsupported control mode " + mode);
+ }
+ }
+
+ @Override
+ public void setSensorPosition(final double position) {
+ revErrorToException(ExceptionTarget.THROW, sensorPositionSetter.apply(position));
+ }
+
+ @Override
+ public void follow(final MotorController leader) {
+ try {
+ revErrorToException(ExceptionTarget.LOG, super.follow((CANSparkMax) leader));
+ } catch (ClassCastException ex) {
+ LoggerExceptionUtils.logException(
+ new IllegalArgumentException(
+ "Spark Max can only follow another Spark Max", ex));
+ }
+ }
+
+ @Override
+ public void setNeutralMode(final NeutralMode neutralMode) {
+ switch (neutralMode) {
+ case Brake:
+ revErrorToException(ExceptionTarget.LOG, setIdleMode(IdleMode.kBrake));
+ break;
+ case Coast:
+ revErrorToException(ExceptionTarget.LOG, setIdleMode(IdleMode.kCoast));
+ break;
+ default:
+ LoggerExceptionUtils.logException(
+ new IllegalArgumentException("Unsupported neutral mode " + neutralMode));
+ break;
+ }
+ }
+
+ @Override
+ public void setP(final double value) {
+ revErrorToException(ExceptionTarget.LOG, getPIDController().setP(value));
+ }
+
+ @Override
+ public void setI(final double value) {
+ revErrorToException(ExceptionTarget.LOG, getPIDController().setI(value));
+ }
+
+ @Override
+ public void setD(final double value) {
+ revErrorToException(ExceptionTarget.LOG, getPIDController().setD(value));
+ }
+
+ @Override
+ public void setFF(final double value) {
+ revErrorToException(ExceptionTarget.LOG, getPIDController().setFF(value));
+ }
+
+ @Override
+ public void setSelectedFeedbackSensor(final FeedbackDevice feedbackDevice) {
+ switch (feedbackDevice) {
+ case Analog:
+ {
+ SparkMaxAnalogSensor analog = getAnalog(SparkMaxAnalogSensor.Mode.kAbsolute);
+ revErrorToException(ExceptionTarget.LOG, analog.setInverted(sensorInverted));
+ sensorPositionSupplier = analog::getPosition;
+ sensorVelocitySupplier = analog::getVelocity;
+ sensorPositionSetter = (pos) -> REVLibError.kOk;
+ sensorInvertedSetter = analog::setInverted;
+ revErrorToException(
+ ExceptionTarget.LOG, getPIDController().setFeedbackDevice(analog));
+ return;
+ }
+ case CTRE_MagEncoder_Absolute:
+ LoggerExceptionUtils.logException(
+ new IllegalArgumentException("SparkMax does not support CTRE Mag Encoder"));
+ case CTRE_MagEncoder_Relative:
+ LoggerExceptionUtils.logException(
+ new IllegalArgumentException("SparkMax does not support CTRE Mag Encoder"));
+ case IntegratedSensor:
+ {
+ RelativeEncoder encoder = getEncoder();
+ // NOTE(rcahoon, 2022-04-19): Don't call this. Trying to call setInverted on the
+ // integrated sensor returns an error.
+ // revErrorToException(ExceptionTarget.LOG,
+ // encoder.setInverted(sensorInverted));
+ sensorPositionSupplier = encoder::getPosition;
+ sensorVelocitySupplier = encoder::getVelocity;
+ sensorPositionSetter = encoder::setPosition;
+ // NOTE(rcahoon, 2022-04-19): Don't call this. Trying to call setInverted on the
+ // integrated sensor returns an error.
+ // sensorInvertedSetter = encoder::setInverted;
+ sensorInvertedSetter = (inverted) -> REVLibError.kOk;
+ revErrorToException(
+ ExceptionTarget.LOG, getPIDController().setFeedbackDevice(encoder));
+ return;
+ }
+ case None:
+ return;
+ case PulseWidthEncodedPosition:
+ LoggerExceptionUtils.logException(
+ new IllegalArgumentException("SparkMax does not support PWM sensors"));
+ case QuadEncoder:
+ // TODO: should we pass a real counts-per-rev scale here?
+ RelativeEncoder encoder = getAlternateEncoder(1);
+ revErrorToException(ExceptionTarget.LOG, encoder.setInverted(sensorInverted));
+ sensorPositionSupplier = encoder::getPosition;
+ sensorVelocitySupplier = encoder::getVelocity;
+ sensorPositionSetter = encoder::setPosition;
+ sensorInvertedSetter = encoder::setInverted;
+ revErrorToException(
+ ExceptionTarget.LOG, getPIDController().setFeedbackDevice(encoder));
+ return;
+ case RemoteSensor0:
+ LoggerExceptionUtils.logException(
+ new IllegalArgumentException("SparkMax does not support remote sensors"));
+ case RemoteSensor1:
+ LoggerExceptionUtils.logException(
+ new IllegalArgumentException("SparkMax does not support remote sensors"));
+ case SensorDifference:
+ LoggerExceptionUtils.logException(
+ new IllegalArgumentException("SparkMax does not support SensorDifference"));
+ case SensorSum:
+ LoggerExceptionUtils.logException(
+ new IllegalArgumentException("SparkMax does not support SensorSum"));
+ case SoftwareEmulatedSensor:
+ LoggerExceptionUtils.logException(
+ new IllegalArgumentException(
+ "SparkMax does not support SoftwareEmulatedSensor"));
+ case Tachometer:
+ LoggerExceptionUtils.logException(
+ new IllegalArgumentException("SparkMax does not support Tachometer"));
+ default:
+ LoggerExceptionUtils.logException(
+ new IllegalArgumentException("Unsupported sensor type " + feedbackDevice));
+ }
+ }
+
+ @Override
+ public void setSensorInverted(final boolean inverted) {
+ sensorInverted = inverted;
+ revErrorToException(ExceptionTarget.LOG, sensorInvertedSetter.apply(inverted));
+ }
+
+ @Override
+ public void setOutputRange(final double minOutput, final double maxOutput) {
+ revErrorToException(
+ ExceptionTarget.LOG, getPIDController().setOutputRange(minOutput, maxOutput));
+ }
+
+ public void setCurrentLimit(final double ampsLimit) {
+ revErrorToException(ExceptionTarget.LOG, setSmartCurrentLimit((int) ampsLimit));
+ }
+
+ @Override
+ public void restoreFactoryDefault() {
+ revErrorToException(ExceptionTarget.LOG, restoreFactoryDefaults());
+ }
+
+ @Override
+ public void setOpenLoopRamp(final double secondsFromNeutralToFull) {
+ revErrorToException(ExceptionTarget.LOG, setOpenLoopRampRate(secondsFromNeutralToFull));
+ }
+
+ @Override
+ public void setClosedLoopRamp(final double secondsFromNeutralToFull) {
+ revErrorToException(ExceptionTarget.LOG, setClosedLoopRampRate(secondsFromNeutralToFull));
+ }
}
diff --git a/src/main/java/com/team766/hal/wpilib/CANTalonFxMotorController.java b/src/main/java/com/team766/hal/wpilib/CANTalonFxMotorController.java
index 148901a7..f1dd05f3 100644
--- a/src/main/java/com/team766/hal/wpilib/CANTalonFxMotorController.java
+++ b/src/main/java/com/team766/hal/wpilib/CANTalonFxMotorController.java
@@ -14,188 +14,194 @@
public class CANTalonFxMotorController extends BaseCTREMotorController implements MotorController {
- private WPI_TalonFX m_device;
- private double m_feedForward = 0.0;
-
- public CANTalonFxMotorController(int deviceNumber, String CANBus) {
- m_device = new WPI_TalonFX(deviceNumber, CANBus);
- }
-
- public CANTalonFxMotorController(final int deviceNumber) {
- m_device = new WPI_TalonFX(deviceNumber);
- }
-
- @Override
- public void set(final ControlMode mode, double value) {
- com.ctre.phoenix.motorcontrol.ControlMode ctre_mode = null;
- boolean useFourTermSet = true;
- switch (mode) {
- case PercentOutput:
- ctre_mode = com.ctre.phoenix.motorcontrol.ControlMode.PercentOutput;
- useFourTermSet = false;
- break;
- case Position:
- ctre_mode = com.ctre.phoenix.motorcontrol.ControlMode.Position;
- break;
- case Velocity:
- // Sensor velocity is measured in units per 100ms.
- value /= 10.0;
- ctre_mode = com.ctre.phoenix.motorcontrol.ControlMode.Velocity;
- break;
- case Current:
- ctre_mode = com.ctre.phoenix.motorcontrol.ControlMode.Current;
- break;
- case Follower:
- ctre_mode = com.ctre.phoenix.motorcontrol.ControlMode.Follower;
- useFourTermSet = false;
- break;
- case MotionProfile:
- ctre_mode = com.ctre.phoenix.motorcontrol.ControlMode.MotionProfile;
- break;
- case MotionMagic:
- ctre_mode = com.ctre.phoenix.motorcontrol.ControlMode.MotionMagic;
- break;
- case MotionProfileArc:
- ctre_mode = com.ctre.phoenix.motorcontrol.ControlMode.MotionProfileArc;
- break;
- case Voltage:
- m_device.setVoltage(value);
- return;
- case Disabled:
- ctre_mode = com.ctre.phoenix.motorcontrol.ControlMode.Disabled;
- useFourTermSet = false;
- break;
- default:
- LoggerExceptionUtils.logException(new UnsupportedOperationException("invalid mode provided. provided value: " + mode));
- break;
- }
- if (ctre_mode == null) {
- Logger.get(Category.HAL).logRaw(Severity.ERROR,
- "CAN ControlMode is not translatable: " + mode);
- ctre_mode = com.ctre.phoenix.motorcontrol.ControlMode.Disabled;
- }
- if (useFourTermSet) {
- m_device.set(ctre_mode, value, DemandType.ArbitraryFeedForward, m_feedForward);
- } else {
- m_device.set(ctre_mode, value);
- }
- }
-
- @Override
- public void stopMotor() {
- m_device.set(com.ctre.phoenix.motorcontrol.ControlMode.PercentOutput, 0);
- }
-
- @Override
- public double getSensorPosition() {
- return m_device.getSelectedSensorPosition(0);
- }
-
- @Override
- public double getSensorVelocity() {
- // Sensor velocity is returned in units per 100ms.
- return m_device.getSelectedSensorVelocity(0) * 10.0;
- }
-
- @Override
- public void setSensorPosition(final double position) {
- errorCodeToException(ExceptionTarget.THROW,
- m_device.setSelectedSensorPosition(position, 0, 0));
- }
-
- @Override
- public void follow(final MotorController leader) {
- try {
- m_device.follow((IMotorController) leader);
- } catch (ClassCastException ex) {
- LoggerExceptionUtils.logException(new IllegalArgumentException(
- "Talon can only follow another CTRE motor controller", ex));
- }
- }
-
- @Override
- public void setOpenLoopRamp(final double secondsFromNeutralToFull) {
- errorCodeToException(ExceptionTarget.LOG,
- m_device.configOpenloopRamp(secondsFromNeutralToFull, TIMEOUT_MS));
- }
-
- @Override
- public void setClosedLoopRamp(final double secondsFromNeutralToFull) {
- errorCodeToException(ExceptionTarget.LOG,
- m_device.configClosedloopRamp(secondsFromNeutralToFull, TIMEOUT_MS));
- }
-
- @Override
- public void setFF(final double value) {
- errorCodeToException(ExceptionTarget.LOG, m_device.config_kF(0, value, TIMEOUT_MS));
- }
-
- @Override
- public void setP(final double value) {
- errorCodeToException(ExceptionTarget.LOG, m_device.config_kP(0, value));
- }
-
- @Override
- public void setI(final double value) {
- errorCodeToException(ExceptionTarget.LOG, m_device.config_kI(0, value));
- }
-
- @Override
- public void setD(final double value) {
- errorCodeToException(ExceptionTarget.LOG, m_device.config_kD(0, value));
- }
-
- @Override
- public void setSelectedFeedbackSensor(final FeedbackDevice feedbackDevice) {
- errorCodeToException(ExceptionTarget.LOG,
- m_device.configSelectedFeedbackSensor(feedbackDevice));
- }
-
- @Override
- public void setSensorInverted(final boolean inverted) {
- m_device.setSensorPhase(inverted);
- }
-
- @Override
- public void setOutputRange(final double minOutput, final double maxOutput) {
- errorCodeToException(ExceptionTarget.LOG, m_device.configPeakOutputReverse(minOutput));
- errorCodeToException(ExceptionTarget.LOG, m_device.configPeakOutputForward(maxOutput));
- }
-
- @Override
- public void setCurrentLimit(final double ampsLimit) {
- errorCodeToException(ExceptionTarget.LOG, m_device.configSupplyCurrentLimit(
- new SupplyCurrentLimitConfiguration(true, ampsLimit, 0, 0.01)));
- }
-
- @Override
- public void restoreFactoryDefault() {
- errorCodeToException(ExceptionTarget.LOG, m_device.configFactoryDefault());
- }
-
- @Override
- public double get() {
- return m_device.get();
- }
-
- @Override
- public void set(final double power) {
- m_device.set(power);
- }
-
- @Override
- public void setInverted(final boolean isInverted) {
- m_device.setInverted(isInverted);
- }
-
- @Override
- public boolean getInverted() {
- return m_device.getInverted();
- }
-
- @Override
- public void setNeutralMode(final NeutralMode neutralMode) {
- m_device.setNeutralMode(neutralMode);
- }
-
+ private WPI_TalonFX m_device;
+ private double m_feedForward = 0.0;
+
+ public CANTalonFxMotorController(int deviceNumber, String CANBus) {
+ m_device = new WPI_TalonFX(deviceNumber, CANBus);
+ }
+
+ public CANTalonFxMotorController(final int deviceNumber) {
+ m_device = new WPI_TalonFX(deviceNumber);
+ }
+
+ @Override
+ public void set(final ControlMode mode, double value) {
+ com.ctre.phoenix.motorcontrol.ControlMode ctre_mode = null;
+ boolean useFourTermSet = true;
+ switch (mode) {
+ case PercentOutput:
+ ctre_mode = com.ctre.phoenix.motorcontrol.ControlMode.PercentOutput;
+ useFourTermSet = false;
+ break;
+ case Position:
+ ctre_mode = com.ctre.phoenix.motorcontrol.ControlMode.Position;
+ break;
+ case Velocity:
+ // Sensor velocity is measured in units per 100ms.
+ value /= 10.0;
+ ctre_mode = com.ctre.phoenix.motorcontrol.ControlMode.Velocity;
+ break;
+ case Current:
+ ctre_mode = com.ctre.phoenix.motorcontrol.ControlMode.Current;
+ break;
+ case Follower:
+ ctre_mode = com.ctre.phoenix.motorcontrol.ControlMode.Follower;
+ useFourTermSet = false;
+ break;
+ case MotionProfile:
+ ctre_mode = com.ctre.phoenix.motorcontrol.ControlMode.MotionProfile;
+ break;
+ case MotionMagic:
+ ctre_mode = com.ctre.phoenix.motorcontrol.ControlMode.MotionMagic;
+ break;
+ case MotionProfileArc:
+ ctre_mode = com.ctre.phoenix.motorcontrol.ControlMode.MotionProfileArc;
+ break;
+ case Voltage:
+ m_device.setVoltage(value);
+ return;
+ case Disabled:
+ ctre_mode = com.ctre.phoenix.motorcontrol.ControlMode.Disabled;
+ useFourTermSet = false;
+ break;
+ default:
+ LoggerExceptionUtils.logException(
+ new UnsupportedOperationException(
+ "invalid mode provided. provided value: " + mode));
+ break;
+ }
+ if (ctre_mode == null) {
+ Logger.get(Category.HAL)
+ .logRaw(Severity.ERROR, "CAN ControlMode is not translatable: " + mode);
+ ctre_mode = com.ctre.phoenix.motorcontrol.ControlMode.Disabled;
+ }
+ if (useFourTermSet) {
+ m_device.set(ctre_mode, value, DemandType.ArbitraryFeedForward, m_feedForward);
+ } else {
+ m_device.set(ctre_mode, value);
+ }
+ }
+
+ @Override
+ public void stopMotor() {
+ m_device.set(com.ctre.phoenix.motorcontrol.ControlMode.PercentOutput, 0);
+ }
+
+ @Override
+ public double getSensorPosition() {
+ return m_device.getSelectedSensorPosition(0);
+ }
+
+ @Override
+ public double getSensorVelocity() {
+ // Sensor velocity is returned in units per 100ms.
+ return m_device.getSelectedSensorVelocity(0) * 10.0;
+ }
+
+ @Override
+ public void setSensorPosition(final double position) {
+ errorCodeToException(
+ ExceptionTarget.THROW, m_device.setSelectedSensorPosition(position, 0, 0));
+ }
+
+ @Override
+ public void follow(final MotorController leader) {
+ try {
+ m_device.follow((IMotorController) leader);
+ } catch (ClassCastException ex) {
+ LoggerExceptionUtils.logException(
+ new IllegalArgumentException(
+ "Talon can only follow another CTRE motor controller", ex));
+ }
+ }
+
+ @Override
+ public void setOpenLoopRamp(final double secondsFromNeutralToFull) {
+ errorCodeToException(
+ ExceptionTarget.LOG,
+ m_device.configOpenloopRamp(secondsFromNeutralToFull, TIMEOUT_MS));
+ }
+
+ @Override
+ public void setClosedLoopRamp(final double secondsFromNeutralToFull) {
+ errorCodeToException(
+ ExceptionTarget.LOG,
+ m_device.configClosedloopRamp(secondsFromNeutralToFull, TIMEOUT_MS));
+ }
+
+ @Override
+ public void setFF(final double value) {
+ errorCodeToException(ExceptionTarget.LOG, m_device.config_kF(0, value, TIMEOUT_MS));
+ }
+
+ @Override
+ public void setP(final double value) {
+ errorCodeToException(ExceptionTarget.LOG, m_device.config_kP(0, value));
+ }
+
+ @Override
+ public void setI(final double value) {
+ errorCodeToException(ExceptionTarget.LOG, m_device.config_kI(0, value));
+ }
+
+ @Override
+ public void setD(final double value) {
+ errorCodeToException(ExceptionTarget.LOG, m_device.config_kD(0, value));
+ }
+
+ @Override
+ public void setSelectedFeedbackSensor(final FeedbackDevice feedbackDevice) {
+ errorCodeToException(
+ ExceptionTarget.LOG, m_device.configSelectedFeedbackSensor(feedbackDevice));
+ }
+
+ @Override
+ public void setSensorInverted(final boolean inverted) {
+ m_device.setSensorPhase(inverted);
+ }
+
+ @Override
+ public void setOutputRange(final double minOutput, final double maxOutput) {
+ errorCodeToException(ExceptionTarget.LOG, m_device.configPeakOutputReverse(minOutput));
+ errorCodeToException(ExceptionTarget.LOG, m_device.configPeakOutputForward(maxOutput));
+ }
+
+ @Override
+ public void setCurrentLimit(final double ampsLimit) {
+ errorCodeToException(
+ ExceptionTarget.LOG,
+ m_device.configSupplyCurrentLimit(
+ new SupplyCurrentLimitConfiguration(true, ampsLimit, 0, 0.01)));
+ }
+
+ @Override
+ public void restoreFactoryDefault() {
+ errorCodeToException(ExceptionTarget.LOG, m_device.configFactoryDefault());
+ }
+
+ @Override
+ public double get() {
+ return m_device.get();
+ }
+
+ @Override
+ public void set(final double power) {
+ m_device.set(power);
+ }
+
+ @Override
+ public void setInverted(final boolean isInverted) {
+ m_device.setInverted(isInverted);
+ }
+
+ @Override
+ public boolean getInverted() {
+ return m_device.getInverted();
+ }
+
+ @Override
+ public void setNeutralMode(final NeutralMode neutralMode) {
+ m_device.setNeutralMode(neutralMode);
+ }
}
diff --git a/src/main/java/com/team766/hal/wpilib/RobotMain.java b/src/main/java/com/team766/hal/wpilib/RobotMain.java
index f275f81e..f3421679 100755
--- a/src/main/java/com/team766/hal/wpilib/RobotMain.java
+++ b/src/main/java/com/team766/hal/wpilib/RobotMain.java
@@ -1,10 +1,7 @@
package com.team766.hal.wpilib;
-import java.io.File;
-//import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.function.Supplier;
import com.team766.config.ConfigFileReader;
+import com.team766.framework.Scheduler;
import com.team766.hal.CanivPoller;
import com.team766.hal.GenericRobotMain;
import com.team766.hal.RobotProvider;
@@ -12,155 +9,160 @@
import edu.wpi.first.wpilibj.Filesystem;
import edu.wpi.first.wpilibj.RobotBase;
import edu.wpi.first.wpilibj.TimedRobot;
+import java.io.File;
+// import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.function.Supplier;
public class RobotMain extends TimedRobot {
- // this file, if present, will be a symlink to one of several config files in the deploy directory.
- // this allows for the same code to be deployed to multiple physical robots, each with their own
- // config file with CAN bus port mappings, etc, with the actual file used for a specific robot
- // to be "selected" via this symlink to the actual file.
- private static final String SELECTED_CONFIG_FILE = "/home/lvuser/selectedConfig.txt";
-
- // if the symlink (above) is not present, back off to this file in the deploy directory.
- private static final String DEFAULT_CONFIG_FILE = "configs/defaultRobotConfig.txt";
-
- // for backwards compatibility, back off to the previous config file location if the above are
- // not
- // found in the deploy directory.
- private static final String LEGACY_CONFIG_FILE = "/home/lvuser/robotConfig.txt";
-
- private GenericRobotMain robot;
-
- public static void main(final String... args) {
- Supplier supplier = new Supplier() {
- RobotMain instance;
-
- @Override
- public RobotMain get() {
- if (instance == null) {
- instance = new RobotMain();
- }
- return instance;
- }
- };
-
- // periodically poll "caniv" in the background, if present
- CanivPoller canivPoller = null;
- if (new File(CanivPoller.CANIV_BIN).exists()) {
- canivPoller = new CanivPoller(10*1000 /* millis */);
- new Thread(canivPoller, "caniv poller").start();
- }
-
- try {
- RobotBase.startRobot(supplier);
- } catch (Throwable ex) {
- ex.printStackTrace();
- LoggerExceptionUtils.logException(ex);
- }
-
- if (canivPoller != null) {
- canivPoller.setDone(true);
- }
- }
-
- public RobotMain() {
- super(0.005);
- }
-
- private static String checkForAndReturnPathToConfigFile(final String file) {
- Path configPath = Filesystem.getDeployDirectory().toPath().resolve(file);
- File configFile = configPath.toFile();
- if (configFile.exists()) {
- return configFile.getPath();
- }
- return null;
- }
-
- @Override
- public void robotInit() {
- try {
- String filename = null;
- filename = checkForAndReturnPathToConfigFile(SELECTED_CONFIG_FILE);
-
- if (filename == null) {
- filename = checkForAndReturnPathToConfigFile(DEFAULT_CONFIG_FILE);
- }
-
- if (filename == null) {
- filename = LEGACY_CONFIG_FILE;
- }
-
- ConfigFileReader.instance = new ConfigFileReader(filename);
- RobotProvider.instance = new WPIRobotProvider();
- robot = new GenericRobotMain();
-
- robot.robotInit();
- } catch (Exception e) {
- e.printStackTrace();
- LoggerExceptionUtils.logException(e);
- }
- }
-
- @Override
- public void disabledInit() {
- try {
- robot.disabledInit();
- } catch (Exception e) {
- e.printStackTrace();
- LoggerExceptionUtils.logException(e);
- }
- }
-
- @Override
- public void autonomousInit() {
- try {
- robot.autonomousInit();
- } catch (Exception e) {
- e.printStackTrace();
- LoggerExceptionUtils.logException(e);
- }
- }
-
- @Override
- public void teleopInit() {
- try {
- robot.teleopInit();
- } catch (Exception e) {
- e.printStackTrace();
- LoggerExceptionUtils.logException(e);
- }
- }
-
- @Override
- public void disabledPeriodic() {
- try {
- robot.disabledPeriodic();
- }catch (Exception e){
- Scheduler.getInstance().run();
- } catch (Exception e) {
- e.printStackTrace();
- LoggerExceptionUtils.logException(e);
- }
- }
-
- @Override
- public void autonomousPeriodic() {
- try {
- robot.autonomousPeriodic();
- Scheduler.getInstance().run();
- } catch (Exception e) {
- e.printStackTrace();
- LoggerExceptionUtils.logException(e);
- }
- }
-
- @Override
- public void teleopPeriodic() {
- try {
- robot.teleopPeriodic();
- Scheduler.getInstance().run();
- } catch (Exception e) {
- e.printStackTrace();
- LoggerExceptionUtils.logException(e);
- }
- }
+ // this file, if present, will be a symlink to one of several config files in the deploy
+ // directory.
+ // this allows for the same code to be deployed to multiple physical robots, each with their own
+ // config file with CAN bus port mappings, etc, with the actual file used for a specific robot
+ // to be "selected" via this symlink to the actual file.
+ private static final String SELECTED_CONFIG_FILE = "/home/lvuser/selectedConfig.txt";
+
+ // if the symlink (above) is not present, back off to this file in the deploy directory.
+ private static final String DEFAULT_CONFIG_FILE = "configs/defaultRobotConfig.txt";
+
+ // for backwards compatibility, back off to the previous config file location if the above are
+ // not
+ // found in the deploy directory.
+ private static final String LEGACY_CONFIG_FILE = "/home/lvuser/robotConfig.txt";
+
+ private GenericRobotMain robot;
+
+ public static void main(final String... args) {
+ Supplier supplier =
+ new Supplier() {
+ RobotMain instance;
+
+ @Override
+ public RobotMain get() {
+ if (instance == null) {
+ instance = new RobotMain();
+ }
+ return instance;
+ }
+ };
+
+ // periodically poll "caniv" in the background, if present
+ CanivPoller canivPoller = null;
+ if (new File(CanivPoller.CANIV_BIN).exists()) {
+ canivPoller = new CanivPoller(10 * 1000 /* millis */);
+ new Thread(canivPoller, "caniv poller").start();
+ }
+
+ try {
+ RobotBase.startRobot(supplier);
+ } catch (Throwable ex) {
+ ex.printStackTrace();
+ LoggerExceptionUtils.logException(ex);
+ }
+
+ if (canivPoller != null) {
+ canivPoller.setDone(true);
+ }
+ }
+
+ public RobotMain() {
+ super(0.005);
+ }
+
+ private static String checkForAndReturnPathToConfigFile(final String file) {
+ Path configPath = Filesystem.getDeployDirectory().toPath().resolve(file);
+ File configFile = configPath.toFile();
+ if (configFile.exists()) {
+ return configFile.getPath();
+ }
+ return null;
+ }
+
+ @Override
+ public void robotInit() {
+ try {
+ String filename = null;
+ filename = checkForAndReturnPathToConfigFile(SELECTED_CONFIG_FILE);
+
+ if (filename == null) {
+ filename = checkForAndReturnPathToConfigFile(DEFAULT_CONFIG_FILE);
+ }
+
+ if (filename == null) {
+ filename = LEGACY_CONFIG_FILE;
+ }
+
+ ConfigFileReader.instance = new ConfigFileReader(filename);
+ RobotProvider.instance = new WPIRobotProvider();
+ robot = new GenericRobotMain();
+
+ robot.robotInit();
+ } catch (Exception e) {
+ e.printStackTrace();
+ LoggerExceptionUtils.logException(e);
+ }
+ }
+
+ @Override
+ public void disabledInit() {
+ try {
+ robot.disabledInit();
+ } catch (Exception e) {
+ e.printStackTrace();
+ LoggerExceptionUtils.logException(e);
+ }
+ }
+
+ @Override
+ public void autonomousInit() {
+ try {
+ robot.autonomousInit();
+ } catch (Exception e) {
+ e.printStackTrace();
+ LoggerExceptionUtils.logException(e);
+ }
+ }
+
+ @Override
+ public void teleopInit() {
+ try {
+ robot.teleopInit();
+ } catch (Exception e) {
+ e.printStackTrace();
+ LoggerExceptionUtils.logException(e);
+ }
+ }
+
+ @Override
+ public void disabledPeriodic() {
+ try {
+ robot.disabledPeriodic();
+ } catch (Exception e) {
+ Scheduler.getInstance().run();
+ e.printStackTrace();
+ LoggerExceptionUtils.logException(e);
+ }
+ }
+
+ @Override
+ public void autonomousPeriodic() {
+ try {
+ robot.autonomousPeriodic();
+ Scheduler.getInstance().run();
+ } catch (Exception e) {
+ e.printStackTrace();
+ LoggerExceptionUtils.logException(e);
+ }
+ }
+
+ @Override
+ public void teleopPeriodic() {
+ try {
+ robot.teleopPeriodic();
+ Scheduler.getInstance().run();
+ } catch (Exception e) {
+ e.printStackTrace();
+ LoggerExceptionUtils.logException(e);
+ }
+ }
}
diff --git a/src/main/java/com/team766/hal/wpilib/Solenoid.java b/src/main/java/com/team766/hal/wpilib/Solenoid.java
index 582f01ff..b1b28255 100755
--- a/src/main/java/com/team766/hal/wpilib/Solenoid.java
+++ b/src/main/java/com/team766/hal/wpilib/Solenoid.java
@@ -1,15 +1,14 @@
package com.team766.hal.wpilib;
import com.team766.hal.SolenoidController;
-
import edu.wpi.first.wpilibj.PneumaticsModuleType;
public class Solenoid extends edu.wpi.first.wpilibj.Solenoid implements SolenoidController {
- public Solenoid(int channel) {
- super(PneumaticsModuleType.REVPH, channel);
- }
+ public Solenoid(int channel) {
+ super(PneumaticsModuleType.REVPH, channel);
+ }
- public Solenoid(int channel, PneumaticsModuleType type) {
- super(type, channel);
- }
+ public Solenoid(int channel, PneumaticsModuleType type) {
+ super(type, channel);
+ }
}
diff --git a/src/main/java/com/team766/hal/wpilib/WPIRobotProvider.java b/src/main/java/com/team766/hal/wpilib/WPIRobotProvider.java
index 227ca1f6..c5241879 100755
--- a/src/main/java/com/team766/hal/wpilib/WPIRobotProvider.java
+++ b/src/main/java/com/team766/hal/wpilib/WPIRobotProvider.java
@@ -1,7 +1,5 @@
package com.team766.hal.wpilib;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
import com.team766.config.ConfigFileReader;
import com.team766.hal.AnalogInputReader;
import com.team766.hal.BeaconReader;
@@ -14,16 +12,16 @@
import com.team766.hal.GyroReader;
import com.team766.hal.JoystickReader;
import com.team766.hal.LocalMotorController;
+import com.team766.hal.MotorController;
import com.team766.hal.PositionReader;
import com.team766.hal.RelayOutput;
import com.team766.hal.RobotProvider;
import com.team766.hal.SolenoidController;
-import com.team766.hal.MotorController;
import com.team766.hal.mock.MockBeaconSensor;
import com.team766.hal.mock.MockGyro;
+import com.team766.hal.mock.MockMotorController;
import com.team766.hal.mock.MockPositionSensor;
import com.team766.library.ValueProvider;
-import com.team766.hal.mock.MockMotorController;
import com.team766.logging.Category;
import com.team766.logging.Logger;
import com.team766.logging.LoggerExceptionUtils;
@@ -36,251 +34,266 @@
import edu.wpi.first.wpilibj.PneumaticsControlModule;
import edu.wpi.first.wpilibj.RobotController;
import edu.wpi.first.wpilibj.SPI;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
public class WPIRobotProvider extends RobotProvider {
- /**
- * Runnable that counts the number of times we receive new data from the driver station. Used as
- * part of impl of {@link #hasNewDriverStationData()}.
- */
- private static class DataRefreshRunnable implements Runnable {
- private final AtomicBoolean m_keepAlive = new AtomicBoolean();
- private final AtomicInteger m_dataCount = new AtomicInteger();
+ /**
+ * Runnable that counts the number of times we receive new data from the driver station. Used as
+ * part of impl of {@link #hasNewDriverStationData()}.
+ */
+ private static class DataRefreshRunnable implements Runnable {
+ private final AtomicBoolean m_keepAlive = new AtomicBoolean();
+ private final AtomicInteger m_dataCount = new AtomicInteger();
+
+ DataRefreshRunnable() {
+ m_keepAlive.set(true);
+ }
- DataRefreshRunnable() {
- m_keepAlive.set(true);
- }
+ public void cancel() {
+ m_keepAlive.set(false);
+ }
- public void cancel() {
- m_keepAlive.set(false);
- }
+ @Override
+ public void run() {
+ // create and register a handle that gets notified whenever there's new DS data.
+ int handle = WPIUtilJNI.createEvent(false, false);
+ DriverStationJNI.provideNewDataEventHandle(handle);
- @Override
- public void run() {
- // create and register a handle that gets notified whenever there's new DS data.
- int handle = WPIUtilJNI.createEvent(false, false);
- DriverStationJNI.provideNewDataEventHandle(handle);
+ while (m_keepAlive.get()) {
+ try {
+ // wait for new data or timeout
+ // (timeout returns true)
+ if (!WPIUtilJNI.waitForObjectTimeout(handle, 0.1)) {
+ m_dataCount.incrementAndGet();
+ }
+ } catch (InterruptedException e) {
+ // should only happen during failures
+ LoggerExceptionUtils.logException(e);
- while (m_keepAlive.get()) {
- try {
- // wait for new data or timeout
- // (timeout returns true)
- if (!WPIUtilJNI.waitForObjectTimeout(handle, 0.1)) {
- m_dataCount.incrementAndGet();
- }
- } catch (InterruptedException e) {
- // should only happen during failures
- LoggerExceptionUtils.logException(e);
+ // clean up handle
+ DriverStationJNI.removeNewDataEventHandle(handle);
+ WPIUtilJNI.destroyEvent(handle);
- // clean up handle
- DriverStationJNI.removeNewDataEventHandle(handle);
- WPIUtilJNI.destroyEvent(handle);
+ // re-register handle in an attempt to keep data flowing.
+ handle = WPIUtilJNI.createEvent(false, false);
+ DriverStationJNI.provideNewDataEventHandle(handle);
+ }
+ }
- // re-register handle in an attempt to keep data flowing.
- handle = WPIUtilJNI.createEvent(false, false);
- DriverStationJNI.provideNewDataEventHandle(handle);
- }
- }
+ DriverStationJNI.removeNewDataEventHandle(handle);
+ WPIUtilJNI.destroyEvent(handle);
+ }
+ }
- DriverStationJNI.removeNewDataEventHandle(handle);
- WPIUtilJNI.destroyEvent(handle);
- }
- }
+ private DataRefreshRunnable m_DataRefreshRunnable = new DataRefreshRunnable();
+ private Thread m_dataRefreshThread;
+ private int m_lastDataCount = 0;
- private DataRefreshRunnable m_DataRefreshRunnable = new DataRefreshRunnable();
- private Thread m_dataRefreshThread;
- private int m_lastDataCount = 0;
+ public WPIRobotProvider() {
+ m_dataRefreshThread = new Thread(m_DataRefreshRunnable, "DataRefreshThread");
+ m_dataRefreshThread.start();
+ try {
+ ph.enableCompressorAnalog(90, 115);
+ } catch (Exception ex) {
+ LoggerExceptionUtils.logException(ex);
+ }
+ }
- public WPIRobotProvider() {
- m_dataRefreshThread = new Thread(m_DataRefreshRunnable, "DataRefreshThread");
- m_dataRefreshThread.start();
- try {
- ph.enableCompressorAnalog(90, 115);
- } catch (Exception ex) {
- LoggerExceptionUtils.logException(ex);
- }
- }
+ private MotorController[][] motors =
+ new MotorController[MotorController.Type.values().length][64];
- private MotorController[][] motors =
- new MotorController[MotorController.Type.values().length][64];
+ // The presence of this object allows the compressor to run before we've declared any solenoids.
+ @SuppressWarnings("unused")
+ private PneumaticsControlModule pcm = new PneumaticsControlModule();
- // The presence of this object allows the compressor to run before we've declared any solenoids.
- @SuppressWarnings("unused")
- private PneumaticsControlModule pcm = new PneumaticsControlModule();
- private PneumaticHub ph = new PneumaticHub();
+ private PneumaticHub ph = new PneumaticHub();
- @Override
- public MotorController getMotor(final int index, final String configPrefix,
- final MotorController.Type type, ControlInputReader localSensor) {
- if (motors[type.ordinal()][index] != null) {
- return motors[type.ordinal()][index];
- }
- MotorController motor = null;
- switch (type) {
- case SparkMax:
- try {
- motor = new CANSparkMaxMotorController(index);
- } catch (Exception ex) {
- LoggerExceptionUtils.logException(ex);
- motor = new LocalMotorController(configPrefix, new MockMotorController(index),
- localSensor);
- localSensor = null;
- }
- break;
- case TalonSRX:
- motor = new CANTalonMotorController(index);
- break;
- case VictorSPX:
- motor = new CANVictorMotorController(index);
- break;
- case TalonFX:
- final ValueProvider CANBus = ConfigFileReader.getInstance().getString(configPrefix + ".CANBus");
- motor = new CANTalonFxMotorController(index, CANBus.get());
- break;
- case VictorSP:
- motor = new LocalMotorController(configPrefix, new PWMVictorSP(index), localSensor);
- localSensor = null;
- break;
- default:
- break;
- }
- if (motor == null) {
- LoggerExceptionUtils
- .logException(new IllegalArgumentException("Unsupported motor type " + type));
- motor = new LocalMotorController(configPrefix, new MockMotorController(index),
- localSensor);
- localSensor = null;
- }
- if (localSensor != null) {
- motor = new LocalMotorController(configPrefix, motor, localSensor);
- }
- motors[type.ordinal()][index] = motor;
- return motor;
- }
+ @Override
+ public MotorController getMotor(
+ final int index,
+ final String configPrefix,
+ final MotorController.Type type,
+ ControlInputReader localSensor) {
+ if (motors[type.ordinal()][index] != null) {
+ return motors[type.ordinal()][index];
+ }
+ MotorController motor = null;
+ switch (type) {
+ case SparkMax:
+ try {
+ motor = new CANSparkMaxMotorController(index);
+ } catch (Exception ex) {
+ LoggerExceptionUtils.logException(ex);
+ motor =
+ new LocalMotorController(
+ configPrefix, new MockMotorController(index), localSensor);
+ localSensor = null;
+ }
+ break;
+ case TalonSRX:
+ motor = new CANTalonMotorController(index);
+ break;
+ case VictorSPX:
+ motor = new CANVictorMotorController(index);
+ break;
+ case TalonFX:
+ final ValueProvider CANBus =
+ ConfigFileReader.getInstance().getString(configPrefix + ".CANBus");
+ motor = new CANTalonFxMotorController(index, CANBus.get());
+ break;
+ case VictorSP:
+ motor = new LocalMotorController(configPrefix, new PWMVictorSP(index), localSensor);
+ localSensor = null;
+ break;
+ default:
+ break;
+ }
+ if (motor == null) {
+ LoggerExceptionUtils.logException(
+ new IllegalArgumentException("Unsupported motor type " + type));
+ motor =
+ new LocalMotorController(
+ configPrefix, new MockMotorController(index), localSensor);
+ localSensor = null;
+ }
+ if (localSensor != null) {
+ motor = new LocalMotorController(configPrefix, motor, localSensor);
+ }
+ motors[type.ordinal()][index] = motor;
+ return motor;
+ }
- @Override
- public EncoderReader getEncoder(int index1, int index2) {
- if (encoders[index1] == null) {
- encoders[index1] = new Encoder(index1, index2);
- }
- return encoders[index1];
- }
+ @Override
+ public EncoderReader getEncoder(int index1, int index2) {
+ if (encoders[index1] == null) {
+ encoders[index1] = new Encoder(index1, index2);
+ }
+ return encoders[index1];
+ }
- @Override
- public SolenoidController getSolenoid(int index) {
- if(solenoids[index] == null) {
- solenoids[index] = new Solenoid(index);
- }
- return solenoids[index];
- }
+ @Override
+ public SolenoidController getSolenoid(int index) {
+ if (solenoids[index] == null) {
+ solenoids[index] = new Solenoid(index);
+ }
+ return solenoids[index];
+ }
- @Override
- // Gyro index values:
- // -1 = Spartan Gyro
- // 0+ = Analog Gyro on port index
- public GyroReader getGyro(final int index) {
- if (gyros[index + 2] == null) {
- if (index < -2) {
- Logger.get(Category.CONFIGURATION).logRaw(Severity.ERROR, "Invalid gyro port "
- + index + ". Must be -2, -1, or a non-negative integer");
- return new MockGyro();
- } else if (index == -2) {
- gyros[index + 2] = new NavXGyro(I2C.Port.kOnboard);
- } else if (index == -1) {
- gyros[index + 2] = new ADXRS450_Gyro(SPI.Port.kOnboardCS0);
- } else {
- gyros[index + 2] = new AnalogGyro(index);
- }
- }
- return gyros[index + 2];
- }
+ @Override
+ // Gyro index values:
+ // -1 = Spartan Gyro
+ // 0+ = Analog Gyro on port index
+ public GyroReader getGyro(final int index) {
+ if (gyros[index + 2] == null) {
+ if (index < -2) {
+ Logger.get(Category.CONFIGURATION)
+ .logRaw(
+ Severity.ERROR,
+ "Invalid gyro port "
+ + index
+ + ". Must be -2, -1, or a non-negative integer");
+ return new MockGyro();
+ } else if (index == -2) {
+ gyros[index + 2] = new NavXGyro(I2C.Port.kOnboard);
+ } else if (index == -1) {
+ gyros[index + 2] = new ADXRS450_Gyro(SPI.Port.kOnboardCS0);
+ } else {
+ gyros[index + 2] = new AnalogGyro(index);
+ }
+ }
+ return gyros[index + 2];
+ }
- @Override
- public CameraReader getCamera(final String id, final String value) {
- System.err.println("Camera support not yet avaible");
- return null;
- }
+ @Override
+ public CameraReader getCamera(final String id, final String value) {
+ System.err.println("Camera support not yet avaible");
+ return null;
+ }
- @Override
- public JoystickReader getJoystick(int index) {
- if (joysticks[index] == null) {
- joysticks[index] = new Joystick(index);
- }
- return joysticks[index];
- }
+ @Override
+ public JoystickReader getJoystick(int index) {
+ if (joysticks[index] == null) {
+ joysticks[index] = new Joystick(index);
+ }
+ return joysticks[index];
+ }
- @Override
- public CameraInterface getCamServer() {
- return new com.team766.hal.wpilib.CameraInterface();
- }
+ @Override
+ public CameraInterface getCamServer() {
+ return new com.team766.hal.wpilib.CameraInterface();
+ }
- @Override
- public DigitalInputReader getDigitalInput(final int index) {
- if (digInputs[index] == null) {
- digInputs[index] = new DigitalInput(index);
- }
- return digInputs[index];
- }
+ @Override
+ public DigitalInputReader getDigitalInput(final int index) {
+ if (digInputs[index] == null) {
+ digInputs[index] = new DigitalInput(index);
+ }
+ return digInputs[index];
+ }
- @Override
- public AnalogInputReader getAnalogInput(int index) {
- if (angInputs[index] == null) {
- angInputs[index] = new AnalogInput(index);
- }
- return angInputs[index];
- }
+ @Override
+ public AnalogInputReader getAnalogInput(int index) {
+ if (angInputs[index] == null) {
+ angInputs[index] = new AnalogInput(index);
+ }
+ return angInputs[index];
+ }
- @Override
- public RelayOutput getRelay(int index) {
- if (relays[index] == null) {
- relays[index] = new Relay(index);
- }
- return relays[index];
- }
+ @Override
+ public RelayOutput getRelay(int index) {
+ if (relays[index] == null) {
+ relays[index] = new Relay(index);
+ }
+ return relays[index];
+ }
- @Override
- public PositionReader getPositionSensor() {
- if (positionSensor == null) {
- positionSensor = new MockPositionSensor();
- Logger.get(Category.CONFIGURATION).logRaw(Severity.ERROR,
- "Position sensor does not exist on real robots. Using mock position sensor instead - it will always return a position of 0");
- }
- return positionSensor;
- }
+ @Override
+ public PositionReader getPositionSensor() {
+ if (positionSensor == null) {
+ positionSensor = new MockPositionSensor();
+ Logger.get(Category.CONFIGURATION)
+ .logRaw(
+ Severity.ERROR,
+ "Position sensor does not exist on real robots. Using mock position sensor instead - it will always return a position of 0");
+ }
+ return positionSensor;
+ }
- @Override
- public BeaconReader getBeaconSensor() {
- if (beaconSensor == null) {
- beaconSensor = new MockBeaconSensor();
- Logger.get(Category.CONFIGURATION).logRaw(
- Severity.ERROR,
- "Beacon sensor does not exist on real robots. Using mock beacon sensor instead - it will always return no beacons"
- );
- }
- return beaconSensor;
- }
+ @Override
+ public BeaconReader getBeaconSensor() {
+ if (beaconSensor == null) {
+ beaconSensor = new MockBeaconSensor();
+ Logger.get(Category.CONFIGURATION)
+ .logRaw(
+ Severity.ERROR,
+ "Beacon sensor does not exist on real robots. Using mock beacon sensor instead - it will always return no beacons");
+ }
+ return beaconSensor;
+ }
- @Override
- public Clock getClock() {
- return SystemClock.instance;
- }
+ @Override
+ public Clock getClock() {
+ return SystemClock.instance;
+ }
- @Override
- public void refreshDriverStationData() {
- DriverStation.refreshData();
- }
+ @Override
+ public void refreshDriverStationData() {
+ DriverStation.refreshData();
+ }
- @Override
- public boolean hasNewDriverStationData() {
- // see if the thread has counted more data changes than the last time this method was called
- int currentDataRefreshThreadCount = m_DataRefreshRunnable.m_dataCount.get();
- boolean dataChanged = m_lastDataCount != currentDataRefreshThreadCount;
- m_lastDataCount = currentDataRefreshThreadCount;
- return dataChanged;
- }
+ @Override
+ public boolean hasNewDriverStationData() {
+ // see if the thread has counted more data changes than the last time this method was called
+ int currentDataRefreshThreadCount = m_DataRefreshRunnable.m_dataCount.get();
+ boolean dataChanged = m_lastDataCount != currentDataRefreshThreadCount;
+ m_lastDataCount = currentDataRefreshThreadCount;
+ return dataChanged;
+ }
- @Override
- public double getBatteryVoltage() {
- return RobotController.getBatteryVoltage();
- }
+ @Override
+ public double getBatteryVoltage() {
+ return RobotController.getBatteryVoltage();
+ }
}
diff --git a/src/main/java/com/team766/library/RateLimiter.java b/src/main/java/com/team766/library/RateLimiter.java
index 18384280..9152331d 100644
--- a/src/main/java/com/team766/library/RateLimiter.java
+++ b/src/main/java/com/team766/library/RateLimiter.java
@@ -10,10 +10,6 @@ public RateLimiter(final double periodSeconds_) {
this.periodSeconds = periodSeconds_;
}
- public void reset() {
- nextTime = 0;
- }
-
public boolean next() {
final double now = RobotProvider.getTimeProvider().get();
if (now > nextTime) {
diff --git a/src/main/java/com/team766/logging/Logger.java b/src/main/java/com/team766/logging/Logger.java
index dcef58e4..0d99de9c 100644
--- a/src/main/java/com/team766/logging/Logger.java
+++ b/src/main/java/com/team766/logging/Logger.java
@@ -1,5 +1,7 @@
package com.team766.logging;
+import com.team766.config.ConfigFileReader;
+import com.team766.library.CircularBuffer;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Collection;
@@ -7,124 +9,132 @@
import java.util.Date;
import java.util.EnumMap;
-import com.team766.config.ConfigFileReader;
-import com.team766.library.CircularBuffer;
-
public final class Logger {
- private static class LogUncaughtException implements Thread.UncaughtExceptionHandler {
- public void uncaughtException(final Thread t, final Throwable e) {
- e.printStackTrace();
-
- LoggerExceptionUtils.logException(e);
-
- if (m_logWriter != null) {
- try {
- m_logWriter.close();
- } catch (Exception e1) {
- e1.printStackTrace();
- }
- }
-
- System.exit(1);
- }
- }
-
- private static final int MAX_NUM_RECENT_ENTRIES = 100;
-
- private static EnumMap m_loggers =
- new EnumMap(Category.class);
- private static LogWriter m_logWriter = null;
- private CircularBuffer m_recentEntries =
- new CircularBuffer(MAX_NUM_RECENT_ENTRIES);
- private static Object s_lastWriteTimeSync = new Object();
- private static long s_lastWriteTime = 0L;
-
- public static String logFilePathBase = null;
-
- static {
- for (Category category : Category.values()) {
- m_loggers.put(category, new Logger(category));
- }
- try {
- ConfigFileReader config_file = ConfigFileReader.getInstance();
- if (config_file != null) {
- logFilePathBase = config_file.getString("logFilePath").get();
- new File(logFilePathBase).mkdirs();
- final String timestamp =
- new SimpleDateFormat("yyyyMMdd'T'HHmmss").format(new Date());
- final String logFilePath = new File(logFilePathBase, timestamp).getAbsolutePath();
- m_logWriter = new LogWriter(logFilePath);
- get(Category.CONFIGURATION).logRaw(Severity.INFO, "Logging to " + logFilePath);
- } else {
- get(Category.CONFIGURATION).logRaw(Severity.ERROR,
- "Config file is not available. Logs will only be in-memory and will be lost when the robot is turned off.");
- }
- } catch (Exception e) {
- e.printStackTrace();
- LoggerExceptionUtils.logException(e);
- }
-
- Thread.setDefaultUncaughtExceptionHandler(new LogUncaughtException());
- }
-
- public static Logger get(final Category category) {
- return m_loggers.get(category);
- }
-
- static long getTime() {
- long nowNanosec = new Date().getTime() * 1000000;
- synchronized (s_lastWriteTimeSync) {
- // Ensure that log entries' timestamps are unique. This is important
- // because the log viewer uses an entry's timestamp as a unique ID,
- // and we don't want two different log entries to accidentally
- // compare as equal.
- nowNanosec = s_lastWriteTime = Math.max(nowNanosec, s_lastWriteTime);
- }
- return nowNanosec;
- }
-
- private final Category m_category;
-
- private Logger(final Category category) {
- m_category = category;
- }
-
- public Collection recentEntries() {
- return Collections.unmodifiableCollection(m_recentEntries);
- }
-
- public void logData(final Severity severity, final String format, final Object... args) {
- var entry = LogEntry.newBuilder().setTime(getTime()).setSeverity(severity)
- .setCategory(m_category);
- entry.setMessageStr(String.format(format, args));
- m_recentEntries.add(entry.build());
- entry.setMessageStr(format);
- for (Object arg : args) {
- var logValue = LogValue.newBuilder();
- SerializationUtils.valueToProto(arg, logValue);
- entry.addArg(logValue.build());
- }
- if (m_logWriter != null) {
- m_logWriter.logStoredFormat(entry);
- }
- }
-
- public void logRaw(final Severity severity, final String message) {
- var entry = LogEntry.newBuilder().setTime(getTime()).setSeverity(severity)
- .setCategory(m_category).setMessageStr(message).build();
- m_recentEntries.add(entry);
- if (m_logWriter != null) {
- m_logWriter.log(entry);
- }
- }
-
- void logOnlyInMemory(final Severity severity, final String message) {
- var entry = LogEntry.newBuilder()
- .setTime(getTime())
- .setSeverity(severity)
- .setCategory(m_category)
- .setMessageStr(message)
- .build();
- m_recentEntries.add(entry);
- }
+ private static class LogUncaughtException implements Thread.UncaughtExceptionHandler {
+ public void uncaughtException(final Thread t, final Throwable e) {
+ e.printStackTrace();
+
+ LoggerExceptionUtils.logException(e);
+
+ if (m_logWriter != null) {
+ try {
+ m_logWriter.close();
+ } catch (Exception e1) {
+ e1.printStackTrace();
+ }
+ }
+
+ System.exit(1);
+ }
+ }
+
+ private static final int MAX_NUM_RECENT_ENTRIES = 100;
+
+ private static EnumMap m_loggers =
+ new EnumMap(Category.class);
+ private static LogWriter m_logWriter = null;
+ private CircularBuffer m_recentEntries =
+ new CircularBuffer(MAX_NUM_RECENT_ENTRIES);
+ private static Object s_lastWriteTimeSync = new Object();
+ private static long s_lastWriteTime = 0L;
+
+ public static String logFilePathBase = null;
+
+ static {
+ for (Category category : Category.values()) {
+ m_loggers.put(category, new Logger(category));
+ }
+ try {
+ ConfigFileReader config_file = ConfigFileReader.getInstance();
+ if (config_file != null) {
+ logFilePathBase = config_file.getString("logFilePath").get();
+ new File(logFilePathBase).mkdirs();
+ final String timestamp =
+ new SimpleDateFormat("yyyyMMdd'T'HHmmss").format(new Date());
+ final String logFilePath = new File(logFilePathBase, timestamp).getAbsolutePath();
+ m_logWriter = new LogWriter(logFilePath);
+ get(Category.CONFIGURATION).logRaw(Severity.INFO, "Logging to " + logFilePath);
+ } else {
+ get(Category.CONFIGURATION)
+ .logRaw(
+ Severity.ERROR,
+ "Config file is not available. Logs will only be in-memory and will be lost when the robot is turned off.");
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ LoggerExceptionUtils.logException(e);
+ }
+
+ Thread.setDefaultUncaughtExceptionHandler(new LogUncaughtException());
+ }
+
+ public static Logger get(final Category category) {
+ return m_loggers.get(category);
+ }
+
+ static long getTime() {
+ long nowNanosec = new Date().getTime() * 1000000;
+ synchronized (s_lastWriteTimeSync) {
+ // Ensure that log entries' timestamps are unique. This is important
+ // because the log viewer uses an entry's timestamp as a unique ID,
+ // and we don't want two different log entries to accidentally
+ // compare as equal.
+ nowNanosec = s_lastWriteTime = Math.max(nowNanosec, s_lastWriteTime);
+ }
+ return nowNanosec;
+ }
+
+ private final Category m_category;
+
+ private Logger(final Category category) {
+ m_category = category;
+ }
+
+ public Collection recentEntries() {
+ return Collections.unmodifiableCollection(m_recentEntries);
+ }
+
+ public void logData(final Severity severity, final String format, final Object... args) {
+ var entry =
+ LogEntry.newBuilder()
+ .setTime(getTime())
+ .setSeverity(severity)
+ .setCategory(m_category);
+ entry.setMessageStr(String.format(format, args));
+ m_recentEntries.add(entry.build());
+ entry.setMessageStr(format);
+ for (Object arg : args) {
+ var logValue = LogValue.newBuilder();
+ SerializationUtils.valueToProto(arg, logValue);
+ entry.addArg(logValue.build());
+ }
+ if (m_logWriter != null) {
+ m_logWriter.logStoredFormat(entry);
+ }
+ }
+
+ public void logRaw(final Severity severity, final String message) {
+ var entry =
+ LogEntry.newBuilder()
+ .setTime(getTime())
+ .setSeverity(severity)
+ .setCategory(m_category)
+ .setMessageStr(message)
+ .build();
+ m_recentEntries.add(entry);
+ if (m_logWriter != null) {
+ m_logWriter.log(entry);
+ }
+ }
+
+ void logOnlyInMemory(final Severity severity, final String message) {
+ var entry =
+ LogEntry.newBuilder()
+ .setTime(getTime())
+ .setSeverity(severity)
+ .setCategory(m_category)
+ .setMessageStr(message)
+ .build();
+ m_recentEntries.add(entry);
+ }
}
diff --git a/src/main/java/com/team766/robot/constants/ExampleInputConstants.java b/src/main/java/com/team766/robot/constants/ExampleInputConstants.java
index aa7cb28b..1ec1734c 100644
--- a/src/main/java/com/team766/robot/constants/ExampleInputConstants.java
+++ b/src/main/java/com/team766/robot/constants/ExampleInputConstants.java
@@ -2,39 +2,38 @@
/**
* Constants used for the Operator Interface, eg for joyticks, buttons, dials, etc.
- *
+ *
*/
public final class ExampleInputConstants {
- // "joysticks"
- public static final int LEFT_JOYSTICK = 0;
- public static final int RIGHT_JOYSTICK = 1;
- public static final int CONTROL_PANEL = 2;
+ // "joysticks"
+ public static final int LEFT_JOYSTICK = 0;
+ public static final int RIGHT_JOYSTICK = 1;
+ public static final int CONTROL_PANEL = 2;
- // navigation
- public static final int AXIS_LEFT_RIGHT = 0;
- public static final int AXIS_FORWARD_BACKWARD = 1;
+ // navigation
+ public static final int AXIS_LEFT_RIGHT = 0;
+ public static final int AXIS_FORWARD_BACKWARD = 1;
+ public static final int AXIS_SHOOTER_DIAL = 3;
- public static final int AXIS_SHOOTER_DIAL = 3;
+ public static final int CONTROL_PANEL_SHOOTER_SWITCH = 1;
+ public static final int CONTROL_PANEL_INTAKE_BUTTON = 2;
- public static final int CONTROL_PANEL_SHOOTER_SWITCH = 1;
- public static final int CONTROL_PANEL_INTAKE_BUTTON = 2;
+ public static final int CONTROL_PANEL_BELTS_BUTTON = 3;
- public static final int CONTROL_PANEL_BELTS_BUTTON = 3;
+ public static final int CONTROL_PANEL_SPITBALL_BUTTON = 14;
- public static final int CONTROL_PANEL_SPITBALL_BUTTON = 14;
-
- public static final int CONTROL_PANEL_AUTOCLIMB_BUTTON = 9;
- public static final int CONTROL_PANEL_ARMS_SWITCH = 13;
- public static final int CONTROL_PANEL_ELEVATOR_UP_BUTTON = 7;
- public static final int CONTROL_PANEL_ELEVATOR_DOWN_BUTTON = 8;
- public static final int CONTROL_PANEL_ELEVATOR_TOP_BUTTON = 5;
- public static final int CONTROL_PANEL_ELEVATOR_BOTTOM_BUTTON = 6;
- public static final int CONTROL_PANEL_AUTO_SHOOT = 10;
- public static final int JOYSTICK_TRIGGER = 1;
- public static final int E_STOP = 11;
- public static final int JOYSTICK_ELEVATOR_RESET_BUTTON = 2;
- public static final int JOYSTICK_CLIMB_RUNG_BUTTON = 4;
- public static final int JOYSTICK_CLIMB_FIRST_RUNG_BUTTON = 5;
+ public static final int CONTROL_PANEL_AUTOCLIMB_BUTTON = 9;
+ public static final int CONTROL_PANEL_ARMS_SWITCH = 13;
+ public static final int CONTROL_PANEL_ELEVATOR_UP_BUTTON = 7;
+ public static final int CONTROL_PANEL_ELEVATOR_DOWN_BUTTON = 8;
+ public static final int CONTROL_PANEL_ELEVATOR_TOP_BUTTON = 5;
+ public static final int CONTROL_PANEL_ELEVATOR_BOTTOM_BUTTON = 6;
+ public static final int CONTROL_PANEL_AUTO_SHOOT = 10;
+ public static final int JOYSTICK_TRIGGER = 1;
+ public static final int E_STOP = 11;
+ public static final int JOYSTICK_ELEVATOR_RESET_BUTTON = 2;
+ public static final int JOYSTICK_CLIMB_RUNG_BUTTON = 4;
+ public static final int JOYSTICK_CLIMB_FIRST_RUNG_BUTTON = 5;
}
diff --git a/src/main/java/com/team766/simulator/ElectricalSystem.java b/src/main/java/com/team766/simulator/ElectricalSystem.java
index 8c08ef8e..7c318d01 100644
--- a/src/main/java/com/team766/simulator/ElectricalSystem.java
+++ b/src/main/java/com/team766/simulator/ElectricalSystem.java
@@ -1,53 +1,55 @@
package com.team766.simulator;
+import com.team766.simulator.interfaces.ElectricalDevice;
import java.util.ArrayList;
import java.util.LinkedHashMap;
-import com.team766.simulator.interfaces.ElectricalDevice;
public class ElectricalSystem {
- private double nominalVoltage = Parameters.BATTERY_VOLTAGE;
- private double primaryResistance = Parameters.PRIMARY_ELECTRICAL_RESISTANCE;
-
- public class BranchInfo {
- public final ElectricalDevice device;
- public ElectricalDevice.Output flow;
-
- public BranchInfo(ElectricalDevice device) {
- this.device = device;
- this.flow = new ElectricalDevice.Output(0);
- }
- }
-
- private ArrayList branchCircuits = new ArrayList();
-
- private ElectricalDevice.Input systemState;
-
- public ElectricalSystem() {
- systemState = new ElectricalDevice.Input(nominalVoltage);
- }
-
- public void addDevice(ElectricalDevice device) {
- branchCircuits.add(new BranchInfo(device));
- }
-
- public void step(double dt) {
- double current = 0.0;
- for (BranchInfo branch : branchCircuits) {
- branch.flow = branch.device.step(systemState, dt);
- current += branch.flow.current;
- }
- systemState = new ElectricalDevice.Input(Math.max(0, nominalVoltage - primaryResistance * current));
- }
-
- public double getSystemVoltage() {
- return systemState.voltage;
- }
-
- public LinkedHashMap getBranchCurrents() {
- var currents = new LinkedHashMap();
- for (BranchInfo branch : branchCircuits) {
- currents.put(branch.device.name(), branch.flow.current);
- }
- return currents;
- }
+ private double nominalVoltage = Parameters.BATTERY_VOLTAGE;
+ private double primaryResistance = Parameters.PRIMARY_ELECTRICAL_RESISTANCE;
+
+ public class BranchInfo {
+ public final ElectricalDevice device;
+ public ElectricalDevice.Output flow;
+
+ public BranchInfo(ElectricalDevice device) {
+ this.device = device;
+ this.flow = new ElectricalDevice.Output(0);
+ }
+ }
+
+ private ArrayList branchCircuits = new ArrayList();
+
+ private ElectricalDevice.Input systemState;
+
+ public ElectricalSystem() {
+ systemState = new ElectricalDevice.Input(nominalVoltage);
+ }
+
+ public void addDevice(ElectricalDevice device) {
+ branchCircuits.add(new BranchInfo(device));
+ }
+
+ public void step(double dt) {
+ double current = 0.0;
+ for (BranchInfo branch : branchCircuits) {
+ branch.flow = branch.device.step(systemState, dt);
+ current += branch.flow.current;
+ }
+ systemState =
+ new ElectricalDevice.Input(
+ Math.max(0, nominalVoltage - primaryResistance * current));
+ }
+
+ public double getSystemVoltage() {
+ return systemState.voltage;
+ }
+
+ public LinkedHashMap getBranchCurrents() {
+ var currents = new LinkedHashMap();
+ for (BranchInfo branch : branchCircuits) {
+ currents.put(branch.device.name(), branch.flow.current);
+ }
+ return currents;
+ }
}
diff --git a/src/main/java/com/team766/simulator/Metrics.java b/src/main/java/com/team766/simulator/Metrics.java
index 82e82cd8..4a0874d2 100644
--- a/src/main/java/com/team766/simulator/Metrics.java
+++ b/src/main/java/com/team766/simulator/Metrics.java
@@ -8,100 +8,101 @@
* Stores a set of parallel time series (i.e. each series has data at the same points in time).
*/
public class Metrics {
- /**
- * This is a handle to a particular time series.
- */
- public class Series {
- private final int index;
+ /**
+ * This is a handle to a particular time series.
+ */
+ public class Series {
+ private final int index;
- private Series(int index) {
- this.index = index;
- }
+ private Series(int index) {
+ this.index = index;
+ }
- private void check(Metrics m) {
- if (m != Metrics.this) {
- throw new IllegalArgumentException();
- }
- }
- }
+ private void check(Metrics m) {
+ if (m != Metrics.this) {
+ throw new IllegalArgumentException();
+ }
+ }
+ }
- /**
- * This is a view on a point in time, with the data for all of the time series at that point.
- */
- public class Point {
- private final Double[] data;
+ /**
+ * This is a view on a point in time, with the data for all of the time series at that point.
+ */
+ public class Point {
+ private final Double[] data;
- private Point(Double[] data) {
- this.data = data;
- }
+ private Point(Double[] data) {
+ this.data = data;
+ }
- /**
- * Set the value in a particular series at this point in time.
- */
- public Point set(Series series, double value) {
- series.check(Metrics.this);
- if (series.index >= 0) {
- data[series.index] = value;
- }
- return this;
- }
- }
+ /**
+ * Set the value in a particular series at this point in time.
+ */
+ public Point set(Series series, double value) {
+ series.check(Metrics.this);
+ if (series.index >= 0) {
+ data[series.index] = value;
+ }
+ return this;
+ }
+ }
- // Each element of this list should be an array of the same length.
- // In each element, index 0 is time and subsequent elements are values.
- private ArrayList metrics = new ArrayList<>();
- // The labels for the series.
- private ArrayList seriesLabels = new ArrayList<>();
+ // Each element of this list should be an array of the same length.
+ // In each element, index 0 is time and subsequent elements are values.
+ private ArrayList metrics = new ArrayList<>();
+ // The labels for the series.
+ private ArrayList seriesLabels = new ArrayList<>();
- /**
- * Add a new data series.
- *
- * This can only be called before data is added.
- *
- * @param name The label for the series.
- * @param enabled If false, data from this series will be ignored. This allows for an easy,
- * centralized way of selecting which data should be processed/visualized.
- * @return A handle to this series, which should be passed when adding data via a Point.
- * @throws IllegalStateException if called after data has been added (via {@link #add()})
- */
- public Series addSeries(String name, boolean enabled) {
- if (metrics.size() != 0) {
- throw new IllegalStateException();
- }
- if (!enabled) {
- return new Series(-1);
- }
- seriesLabels.add(name);
- return new Series(seriesLabels.size());
- }
- public Series addSeries(String name) {
- return addSeries(name, true);
- }
+ /**
+ * Add a new data series.
+ *
+ * This can only be called before data is added.
+ *
+ * @param name The label for the series.
+ * @param enabled If false, data from this series will be ignored. This allows for an easy,
+ * centralized way of selecting which data should be processed/visualized.
+ * @return A handle to this series, which should be passed when adding data via a Point.
+ * @throws IllegalStateException if called after data has been added (via {@link #add()})
+ */
+ public Series addSeries(String name, boolean enabled) {
+ if (metrics.size() != 0) {
+ throw new IllegalStateException();
+ }
+ if (!enabled) {
+ return new Series(-1);
+ }
+ seriesLabels.add(name);
+ return new Series(seriesLabels.size());
+ }
- /**
- * Add a new data point.
- *
- * Data points should be added in order of increasing time.
- */
- public Point add(double time) {
- var data = new Double[seriesLabels.size() + 1];
- data[0] = time;
- metrics.add(data);
- return new Point(data);
- }
+ public Series addSeries(String name) {
+ return addSeries(name, true);
+ }
- /**
- * Remove all data points (Series are not removed)
- */
- public void clear() {
- metrics.clear();
- }
+ /**
+ * Add a new data point.
+ *
+ * Data points should be added in order of increasing time.
+ */
+ public Point add(double time) {
+ var data = new Double[seriesLabels.size() + 1];
+ data[0] = time;
+ metrics.add(data);
+ return new Point(data);
+ }
- public List getMetrics() {
- return Collections.unmodifiableList(metrics);
- }
+ /**
+ * Remove all data points (Series are not removed)
+ */
+ public void clear() {
+ metrics.clear();
+ }
- public List getLabels() {
- return Collections.unmodifiableList(seriesLabels);
- }
+ public List getMetrics() {
+ return Collections.unmodifiableList(metrics);
+ }
+
+ public List getLabels() {
+ return Collections.unmodifiableList(seriesLabels);
+ }
}
diff --git a/src/main/java/com/team766/simulator/Parameters.java b/src/main/java/com/team766/simulator/Parameters.java
index 8c8a84d2..6d3814cd 100644
--- a/src/main/java/com/team766/simulator/Parameters.java
+++ b/src/main/java/com/team766/simulator/Parameters.java
@@ -1,30 +1,33 @@
package com.team766.simulator;
public class Parameters {
- public static final double TIME_STEP = 0.0001; // seconds
- public static final double DURATION = 10.0; // seconds
+ public static final double TIME_STEP = 0.0001; // seconds
+ public static final double DURATION = 10.0; // seconds
- public static final double LOGGING_PERIOD = 0.005; // seconds
+ public static final double LOGGING_PERIOD = 0.005; // seconds
- // Robot mode to run in the simulator
- public static final ProgramInterface.RobotMode INITIAL_ROBOT_MODE = ProgramInterface.RobotMode.AUTON;
+ // Robot mode to run in the simulator
+ public static final ProgramInterface.RobotMode INITIAL_ROBOT_MODE =
+ ProgramInterface.RobotMode.AUTON;
- public static final double BATTERY_VOLTAGE = 12.6; // volts
- public static final double PRIMARY_ELECTRICAL_RESISTANCE = 0.018 + 0.01; // ohms
+ public static final double BATTERY_VOLTAGE = 12.6; // volts
+ public static final double PRIMARY_ELECTRICAL_RESISTANCE = 0.018 + 0.01; // ohms
- public static final double STARTING_PRESSURE = 0; // pascals (relative); 120 psi = 827370.875 pascals
+ public static final double STARTING_PRESSURE =
+ 0; // pascals (relative); 120 psi = 827370.875 pascals
- public static final double DRIVE_WHEEL_DIAMETER = 0.1524; // 6 inches in meters
- public static final double DRIVE_GEAR_RATIO = 8.0;
- public static final int NUM_LOADED_WHEELS = 2;
- public static final int ENCODER_TICKS_PER_REVOLUTION = 256;
+ public static final double DRIVE_WHEEL_DIAMETER = 0.1524; // 6 inches in meters
+ public static final double DRIVE_GEAR_RATIO = 8.0;
+ public static final int NUM_LOADED_WHEELS = 2;
+ public static final int ENCODER_TICKS_PER_REVOLUTION = 256;
- public static final double ROBOT_MASS = 66; // approx. 145 lbs in kg
- public static final double ROBOT_LENGTH = 1.0; // meters
- public static final double ROBOT_WIDTH = 0.8; // meters
- public static final double ROBOT_MOMENT_OF_INERTIA = 1.0 / 12.0 * ROBOT_MASS * (ROBOT_WIDTH * ROBOT_WIDTH + ROBOT_LENGTH * ROBOT_LENGTH);
+ public static final double ROBOT_MASS = 66; // approx. 145 lbs in kg
+ public static final double ROBOT_LENGTH = 1.0; // meters
+ public static final double ROBOT_WIDTH = 0.8; // meters
+ public static final double ROBOT_MOMENT_OF_INERTIA =
+ 1.0 / 12.0 * ROBOT_MASS * (ROBOT_WIDTH * ROBOT_WIDTH + ROBOT_LENGTH * ROBOT_LENGTH);
- public static final double WHEEL_COEFFICIENT_OF_FRICTION = 1.1;
- public static final double ROLLING_RESISTANCE = 0.09;
- public static final double TURNING_RESISTANCE_FACTOR = 0.15;
+ public static final double WHEEL_COEFFICIENT_OF_FRICTION = 1.1;
+ public static final double ROLLING_RESISTANCE = 0.09;
+ public static final double TURNING_RESISTANCE_FACTOR = 0.15;
}
diff --git a/src/main/java/com/team766/simulator/PneumaticsSystem.java b/src/main/java/com/team766/simulator/PneumaticsSystem.java
index 54f63029..092a7b0c 100644
--- a/src/main/java/com/team766/simulator/PneumaticsSystem.java
+++ b/src/main/java/com/team766/simulator/PneumaticsSystem.java
@@ -1,61 +1,67 @@
package com.team766.simulator;
-import java.util.ArrayList;
-
import com.team766.simulator.interfaces.PneumaticDevice;
+import java.util.ArrayList;
public class PneumaticsSystem {
- public static final double PSI_TO_PASCALS = 6894.75729;
-
- private static class BranchCircuit {
- public PneumaticDevice device;
- public double regulatedPressure;
- }
-
- private ArrayList branchCircuits = new ArrayList();
-
- private double systemPressure = Parameters.STARTING_PRESSURE;
- private double compressedAirVolume = 0.0;
- private boolean initialized = false;
-
- public void addDevice(final PneumaticDevice device, final double regulatedPressure) {
- BranchCircuit circuit = new BranchCircuit();
- circuit.device = device;
- circuit.regulatedPressure = regulatedPressure;
- branchCircuits.add(circuit);
- }
-
- public void step(double dt) {
- double flowVolume = 0.0;
- double systemVolume = 0.0;
- for (BranchCircuit circuit : branchCircuits) {
- double devicePressure = Math.min(circuit.regulatedPressure, systemPressure);
- PneumaticDevice.Input inputState = new PneumaticDevice.Input(devicePressure);
- PneumaticDevice.Output deviceState = circuit.device.step(inputState, dt);
- // TODO: implement relieving pressure regulator (make sure device pressure doesn't exceed
- // circuit.regulatedPressure, even when including flow volume)
- flowVolume += deviceState.flowVolume;
- systemVolume += deviceState.deviceVolume;
- }
- compressedAirVolume += flowVolume;
- if (systemVolume == 0.) {
- throw new RuntimeException("Your pneumatics system has no storage volume");
- }
- if (!initialized) {
- compressedAirVolume = systemVolume * (systemPressure + PhysicalConstants.ATMOSPHERIC_PRESSURE) / PhysicalConstants.ATMOSPHERIC_PRESSURE;
- initialized = true;
- }
- systemPressure = compressedAirVolume / systemVolume * PhysicalConstants.ATMOSPHERIC_PRESSURE - PhysicalConstants.ATMOSPHERIC_PRESSURE;
- }
-
- public double getSystemPressure() {
- return systemPressure;
- }
-
- // Simulate the system venting all of its compressed air (e.g. someone opened the release valve;
- // to simulate the pneumatics system becoming compromised, call this method on every simulation tick)
- public void ventPressure() {
- systemPressure = 0;
- initialized = false;
- }
+ public static final double PSI_TO_PASCALS = 6894.75729;
+
+ private static class BranchCircuit {
+ public PneumaticDevice device;
+ public double regulatedPressure;
+ }
+
+ private ArrayList branchCircuits = new ArrayList();
+
+ private double systemPressure = Parameters.STARTING_PRESSURE;
+ private double compressedAirVolume = 0.0;
+ private boolean initialized = false;
+
+ public void addDevice(final PneumaticDevice device, final double regulatedPressure) {
+ BranchCircuit circuit = new BranchCircuit();
+ circuit.device = device;
+ circuit.regulatedPressure = regulatedPressure;
+ branchCircuits.add(circuit);
+ }
+
+ public void step(double dt) {
+ double flowVolume = 0.0;
+ double systemVolume = 0.0;
+ for (BranchCircuit circuit : branchCircuits) {
+ double devicePressure = Math.min(circuit.regulatedPressure, systemPressure);
+ PneumaticDevice.Input inputState = new PneumaticDevice.Input(devicePressure);
+ PneumaticDevice.Output deviceState = circuit.device.step(inputState, dt);
+ // TODO: implement relieving pressure regulator (make sure device pressure doesn't
+ // exceed
+ // circuit.regulatedPressure, even when including flow volume)
+ flowVolume += deviceState.flowVolume;
+ systemVolume += deviceState.deviceVolume;
+ }
+ compressedAirVolume += flowVolume;
+ if (systemVolume == 0.) {
+ throw new RuntimeException("Your pneumatics system has no storage volume");
+ }
+ if (!initialized) {
+ compressedAirVolume =
+ systemVolume
+ * (systemPressure + PhysicalConstants.ATMOSPHERIC_PRESSURE)
+ / PhysicalConstants.ATMOSPHERIC_PRESSURE;
+ initialized = true;
+ }
+ systemPressure =
+ compressedAirVolume / systemVolume * PhysicalConstants.ATMOSPHERIC_PRESSURE
+ - PhysicalConstants.ATMOSPHERIC_PRESSURE;
+ }
+
+ public double getSystemPressure() {
+ return systemPressure;
+ }
+
+ // Simulate the system venting all of its compressed air (e.g. someone opened the release valve;
+ // to simulate the pneumatics system becoming compromised, call this method on every simulation
+ // tick)
+ public void ventPressure() {
+ systemPressure = 0;
+ initialized = false;
+ }
}
diff --git a/src/main/java/com/team766/simulator/Program.java b/src/main/java/com/team766/simulator/Program.java
index 4c1e779a..c6753fde 100644
--- a/src/main/java/com/team766/simulator/Program.java
+++ b/src/main/java/com/team766/simulator/Program.java
@@ -1,7 +1,7 @@
package com.team766.simulator;
public interface Program {
- public void step(double dt);
+ public void step(double dt);
- void reset();
+ void reset();
}
diff --git a/src/main/java/com/team766/simulator/Simulator.java b/src/main/java/com/team766/simulator/Simulator.java
index 0d5e313c..05ab17bd 100644
--- a/src/main/java/com/team766/simulator/Simulator.java
+++ b/src/main/java/com/team766/simulator/Simulator.java
@@ -1,127 +1,143 @@
package com.team766.simulator;
+import com.team766.simulator.elements.*;
+import com.team766.simulator.mechanisms.*;
+import com.team766.simulator.ui.*;
import java.util.ArrayList;
import java.util.HashMap;
import org.apache.commons.math3.geometry.euclidean.threed.RotationConvention;
import org.apache.commons.math3.geometry.euclidean.threed.RotationOrder;
-import com.team766.simulator.elements.*;
-import com.team766.simulator.mechanisms.*;
-import com.team766.simulator.ui.*;
-
public class Simulator implements Runnable {
- private ElectricalSystem electricalSystem = new ElectricalSystem();
- private PneumaticsSystem pneumaticsSystem = new PneumaticsSystem();
- private WestCoastDrive drive = new WestCoastDrive(electricalSystem);
- private AirCompressor compressor = new AirCompressor();
- private DoubleJointedArm arm = new DoubleJointedArm(electricalSystem);
-
- private double time;
- private double nextLogTime;
-
- private final Metrics metrics = new Metrics();
- private final Metrics.Series xPositionSeries = metrics.addSeries("X Position (m)", false);
- private final Metrics.Series yPositionSeries = metrics.addSeries("Y Position (m)", false);
- private final Metrics.Series rotationSeries = metrics.addSeries("Rotation (deg)", false);
- private final Metrics.Series velocitySeries = metrics.addSeries("Velocity (m/s)", false);
- private final Metrics.Series accelerationSeries = metrics.addSeries("Acceleration (m/s^2)", false);
- private final Metrics.Series armAngle1Series = metrics.addSeries("Arm angle 1");
- private final Metrics.Series armAngle2Series = metrics.addSeries("Arm angle 2");
- private final Metrics.Series armVelocity1Series = metrics.addSeries("Arm velocity 1");
- private final Metrics.Series armVelocity2Series = metrics.addSeries("Arm velocity 2");
- private final Metrics.Series systemVoltageSeries = metrics.addSeries("System Voltage (V)", false);
- private final Metrics.Series systemPressureSeries = metrics.addSeries("System Pressure (PSI)", false);
- private final HashMap branchCurrentSeries = new HashMap<>();
-
- private ArrayList driveTrajectory = new ArrayList();
- private ArrayList armTrajectory = new ArrayList();
-
- public Simulator() {
- // NOTE(rcahoon, 2023-02-09): Disabled the compressor so it won't affect the battery voltage
- // available to the arm's motors. This should be re-enabled if we want to simulate
- // pneumatics again.
- //electricalSystem.addDevice(compressor);
- pneumaticsSystem.addDevice(compressor, 120 * PneumaticsSystem.PSI_TO_PASCALS);
- pneumaticsSystem.addDevice(new AirReservoir(0.000574), 120 * PneumaticsSystem.PSI_TO_PASCALS); // 574 mL
- pneumaticsSystem.addDevice(new AirReservoir(0.000574), 120 * PneumaticsSystem.PSI_TO_PASCALS); // 574 mL
-
- for (String branchName : electricalSystem.getBranchCurrents().keySet()) {
- branchCurrentSeries.put(branchName, metrics.addSeries(branchName, false));
- }
- }
-
- public void step() {
- double dt = Parameters.TIME_STEP;
- time += dt;
- ProgramInterface.simulationTime = time;
-
- electricalSystem.step(dt);
- pneumaticsSystem.step(dt);
- drive.step(dt);
- arm.step(dt);
-
- if (ProgramInterface.program != null) {
- ProgramInterface.program.step(dt);
- }
-
- if (nextLogTime <= time) {
- nextLogTime += Parameters.LOGGING_PERIOD;
-
- var metricsPoint = metrics.add(time);
- metricsPoint.set(xPositionSeries, drive.getPosition().getX());
- metricsPoint.set(yPositionSeries, drive.getPosition().getY());
- metricsPoint.set(rotationSeries, Math.toDegrees(drive.getRotation().getAngles(RotationOrder.XYZ, RotationConvention.VECTOR_OPERATOR)[2]));
- metricsPoint.set(velocitySeries, drive.getLinearVelocity().getX());
- metricsPoint.set(accelerationSeries, drive.getLinearAcceleration().getX());
- metricsPoint.set(armAngle1Series, arm.getJ1Angle());
- metricsPoint.set(armAngle2Series, arm.getJ2Angle());
- metricsPoint.set(armVelocity1Series, arm.getJ1Velocity());
- metricsPoint.set(armVelocity2Series, arm.getJ2Velocity());
- metricsPoint.set(systemVoltageSeries, electricalSystem.getSystemVoltage());
- metricsPoint.set(systemPressureSeries, pneumaticsSystem.getSystemPressure() / PneumaticsSystem.PSI_TO_PASCALS);
- for (var branch : electricalSystem.getBranchCurrents().entrySet()) {
- metricsPoint.set(branchCurrentSeries.get(branch.getKey()), branch.getValue());
- }
-
- driveTrajectory.add(new Double[] {
- time,
- drive.getPosition().getX(),
- drive.getPosition().getY(),
- drive.getRotation().getAngles(RotationOrder.XYZ, RotationConvention.VECTOR_OPERATOR)[2],
- drive.getLinearVelocity().getX(),
- drive.getLinearVelocity().getY(),
- });
-
- armTrajectory.add(new Double[] {
- time,
- arm.getJ1Position().get(0, 0),
- arm.getJ1Position().get(1, 0),
- arm.getJ2Position().get(0, 0),
- arm.getJ2Position().get(1, 0)
- });
- }
- }
-
- public void run() {
- metrics.clear();
- time = 0.0;
- nextLogTime = 0.0;
- while (time <= Parameters.DURATION) {
- step();
- if (Math.abs(time - 3.0) < Parameters.TIME_STEP) {
- pneumaticsSystem.ventPressure();
- }
- }
-
- var playbackTimer = new PlaybackTimer(time);
-
- // var trajectoryPanel = new Trajectory(driveTrajectory, playbackTimer);
- // new UIFrame("Trajectory", trajectoryPanel);
-
- var armTrajectoryPanel = new ArmTrajectory(armTrajectory, playbackTimer);
- new UIFrame("ArmTrajectory", armTrajectoryPanel);
-
- var metricsPanel = new MetricsPlot(metrics, playbackTimer);
- new UIFrame("Metrics", metricsPanel);
- }
+ private ElectricalSystem electricalSystem = new ElectricalSystem();
+ private PneumaticsSystem pneumaticsSystem = new PneumaticsSystem();
+ private WestCoastDrive drive = new WestCoastDrive(electricalSystem);
+ private AirCompressor compressor = new AirCompressor();
+ private DoubleJointedArm arm = new DoubleJointedArm(electricalSystem);
+
+ private double time;
+ private double nextLogTime;
+
+ private final Metrics metrics = new Metrics();
+ private final Metrics.Series xPositionSeries = metrics.addSeries("X Position (m)", false);
+ private final Metrics.Series yPositionSeries = metrics.addSeries("Y Position (m)", false);
+ private final Metrics.Series rotationSeries = metrics.addSeries("Rotation (deg)", false);
+ private final Metrics.Series velocitySeries = metrics.addSeries("Velocity (m/s)", false);
+ private final Metrics.Series accelerationSeries =
+ metrics.addSeries("Acceleration (m/s^2)", false);
+ private final Metrics.Series armAngle1Series = metrics.addSeries("Arm angle 1");
+ private final Metrics.Series armAngle2Series = metrics.addSeries("Arm angle 2");
+ private final Metrics.Series armVelocity1Series = metrics.addSeries("Arm velocity 1");
+ private final Metrics.Series armVelocity2Series = metrics.addSeries("Arm velocity 2");
+ private final Metrics.Series systemVoltageSeries =
+ metrics.addSeries("System Voltage (V)", false);
+ private final Metrics.Series systemPressureSeries =
+ metrics.addSeries("System Pressure (PSI)", false);
+ private final HashMap branchCurrentSeries = new HashMap<>();
+
+ private ArrayList driveTrajectory = new ArrayList();
+ private ArrayList armTrajectory = new ArrayList();
+
+ public Simulator() {
+ // NOTE(rcahoon, 2023-02-09): Disabled the compressor so it won't affect the battery voltage
+ // available to the arm's motors. This should be re-enabled if we want to simulate
+ // pneumatics again.
+ // electricalSystem.addDevice(compressor);
+ pneumaticsSystem.addDevice(compressor, 120 * PneumaticsSystem.PSI_TO_PASCALS);
+ pneumaticsSystem.addDevice(
+ new AirReservoir(0.000574), 120 * PneumaticsSystem.PSI_TO_PASCALS); // 574 mL
+ pneumaticsSystem.addDevice(
+ new AirReservoir(0.000574), 120 * PneumaticsSystem.PSI_TO_PASCALS); // 574 mL
+
+ for (String branchName : electricalSystem.getBranchCurrents().keySet()) {
+ branchCurrentSeries.put(branchName, metrics.addSeries(branchName, false));
+ }
+ }
+
+ public void step() {
+ double dt = Parameters.TIME_STEP;
+ time += dt;
+ ProgramInterface.simulationTime = time;
+
+ electricalSystem.step(dt);
+ pneumaticsSystem.step(dt);
+ drive.step(dt);
+ arm.step(dt);
+
+ if (ProgramInterface.program != null) {
+ ProgramInterface.program.step(dt);
+ }
+
+ if (nextLogTime <= time) {
+ nextLogTime += Parameters.LOGGING_PERIOD;
+
+ var metricsPoint = metrics.add(time);
+ metricsPoint.set(xPositionSeries, drive.getPosition().getX());
+ metricsPoint.set(yPositionSeries, drive.getPosition().getY());
+ metricsPoint.set(
+ rotationSeries,
+ Math.toDegrees(
+ drive.getRotation()
+ .getAngles(
+ RotationOrder.XYZ, RotationConvention.VECTOR_OPERATOR)[
+ 2]));
+ metricsPoint.set(velocitySeries, drive.getLinearVelocity().getX());
+ metricsPoint.set(accelerationSeries, drive.getLinearAcceleration().getX());
+ metricsPoint.set(armAngle1Series, arm.getJ1Angle());
+ metricsPoint.set(armAngle2Series, arm.getJ2Angle());
+ metricsPoint.set(armVelocity1Series, arm.getJ1Velocity());
+ metricsPoint.set(armVelocity2Series, arm.getJ2Velocity());
+ metricsPoint.set(systemVoltageSeries, electricalSystem.getSystemVoltage());
+ metricsPoint.set(
+ systemPressureSeries,
+ pneumaticsSystem.getSystemPressure() / PneumaticsSystem.PSI_TO_PASCALS);
+ for (var branch : electricalSystem.getBranchCurrents().entrySet()) {
+ metricsPoint.set(branchCurrentSeries.get(branch.getKey()), branch.getValue());
+ }
+
+ driveTrajectory.add(
+ new Double[] {
+ time,
+ drive.getPosition().getX(),
+ drive.getPosition().getY(),
+ drive.getRotation()
+ .getAngles(RotationOrder.XYZ, RotationConvention.VECTOR_OPERATOR)[
+ 2],
+ drive.getLinearVelocity().getX(),
+ drive.getLinearVelocity().getY(),
+ });
+
+ armTrajectory.add(
+ new Double[] {
+ time,
+ arm.getJ1Position().get(0, 0),
+ arm.getJ1Position().get(1, 0),
+ arm.getJ2Position().get(0, 0),
+ arm.getJ2Position().get(1, 0)
+ });
+ }
+ }
+
+ public void run() {
+ metrics.clear();
+ time = 0.0;
+ nextLogTime = 0.0;
+ while (time <= Parameters.DURATION) {
+ step();
+ if (Math.abs(time - 3.0) < Parameters.TIME_STEP) {
+ pneumaticsSystem.ventPressure();
+ }
+ }
+
+ var playbackTimer = new PlaybackTimer(time);
+
+ // var trajectoryPanel = new Trajectory(driveTrajectory, playbackTimer);
+ // new UIFrame("Trajectory", trajectoryPanel);
+
+ var armTrajectoryPanel = new ArmTrajectory(armTrajectory, playbackTimer);
+ new UIFrame("ArmTrajectory", armTrajectoryPanel);
+
+ var metricsPanel = new MetricsPlot(metrics, playbackTimer);
+ new UIFrame("Metrics", metricsPanel);
+ }
}
diff --git a/src/main/java/com/team766/simulator/elements/AirCompressor.java b/src/main/java/com/team766/simulator/elements/AirCompressor.java
index 275c1519..a3367021 100644
--- a/src/main/java/com/team766/simulator/elements/AirCompressor.java
+++ b/src/main/java/com/team766/simulator/elements/AirCompressor.java
@@ -1,60 +1,68 @@
package com.team766.simulator.elements;
-import java.util.Arrays;
-
-import org.apache.commons.math3.analysis.interpolation.LinearInterpolator;
-import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction;
-
import com.team766.simulator.PneumaticsSystem;
import com.team766.simulator.interfaces.ElectricalDevice;
import com.team766.simulator.interfaces.PneumaticDevice;
+import java.util.Arrays;
+import org.apache.commons.math3.analysis.interpolation.LinearInterpolator;
+import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction;
public class AirCompressor implements ElectricalDevice, PneumaticDevice {
- private static final double CFM_TO_M3_PER_SEC = 0.000471947443;
-
- // Values for http://www.andymark.com/product-p/am-2005.htm
- private static final double NOMINAL_VOLTAGE = 12;
- private static final double[] PRESSURES_PSI = {0., 10., 20., 30., 40., 50., 60., 70., 80., 90., 100., 110.};
- private static final double[] FLOW_RATES_CFM = {0.88, 0.50, 0.43, 0.36, 0.30, 0.27, 0.25, 0.24, 0.24, 0.23, 0.22, 0.22};
- private static final double[] CURRENTS = {7., 8., 8., 9., 9., 9., 10., 10., 10., 11., 11., 10.};
- private static final double[] PRESSURES = Arrays.stream(PRESSURES_PSI).map(psi -> psi * PneumaticsSystem.PSI_TO_PASCALS).toArray();
- private static final double[] FLOW_RATES = Arrays.stream(FLOW_RATES_CFM).map(cfm -> cfm * CFM_TO_M3_PER_SEC).toArray();
-
- PolynomialSplineFunction currentFunction = new LinearInterpolator().interpolate(PRESSURES, CURRENTS);
- PolynomialSplineFunction flowRateFunction = new LinearInterpolator().interpolate(PRESSURES, FLOW_RATES);
-
- private boolean isOn = true;
-
- private ElectricalDevice.Input electricalState = new ElectricalDevice.Input(0);
- private PneumaticDevice.Input pneumaticState = new PneumaticDevice.Input(0);
-
- public void setIsOn(final boolean on) {
- isOn = on;
- }
-
- @Override
- public ElectricalDevice.Output step(ElectricalDevice.Input input, double dt) {
- electricalState = input;
- if (isOn) {
- double nominalCurrent = currentFunction.value(pneumaticState.pressure);
- double adjustedCurrent = nominalCurrent * electricalState.voltage / NOMINAL_VOLTAGE;
- return new ElectricalDevice.Output(adjustedCurrent);
- } else {
- return new ElectricalDevice.Output(0);
- }
- }
-
- @Override
- public PneumaticDevice.Output step(PneumaticDevice.Input input, double dt) {
- pneumaticState = input;
- double nominalFlowRate = flowRateFunction.value(pneumaticState.pressure);
- double adjustedFlowRate = nominalFlowRate * electricalState.voltage / NOMINAL_VOLTAGE;
- double flowVolume = adjustedFlowRate * dt;
- return new PneumaticDevice.Output(flowVolume, 0);
- }
-
- @Override
- public String name() {
- return "AirCompressor";
- }
+ private static final double CFM_TO_M3_PER_SEC = 0.000471947443;
+
+ // Values for http://www.andymark.com/product-p/am-2005.htm
+ private static final double NOMINAL_VOLTAGE = 12;
+ private static final double[] PRESSURES_PSI = {
+ 0., 10., 20., 30., 40., 50., 60., 70., 80., 90., 100., 110.
+ };
+ private static final double[] FLOW_RATES_CFM = {
+ 0.88, 0.50, 0.43, 0.36, 0.30, 0.27, 0.25, 0.24, 0.24, 0.23, 0.22, 0.22
+ };
+ private static final double[] CURRENTS = {7., 8., 8., 9., 9., 9., 10., 10., 10., 11., 11., 10.};
+ private static final double[] PRESSURES =
+ Arrays.stream(PRESSURES_PSI)
+ .map(psi -> psi * PneumaticsSystem.PSI_TO_PASCALS)
+ .toArray();
+ private static final double[] FLOW_RATES =
+ Arrays.stream(FLOW_RATES_CFM).map(cfm -> cfm * CFM_TO_M3_PER_SEC).toArray();
+
+ PolynomialSplineFunction currentFunction =
+ new LinearInterpolator().interpolate(PRESSURES, CURRENTS);
+ PolynomialSplineFunction flowRateFunction =
+ new LinearInterpolator().interpolate(PRESSURES, FLOW_RATES);
+
+ private boolean isOn = true;
+
+ private ElectricalDevice.Input electricalState = new ElectricalDevice.Input(0);
+ private PneumaticDevice.Input pneumaticState = new PneumaticDevice.Input(0);
+
+ public void setIsOn(final boolean on) {
+ isOn = on;
+ }
+
+ @Override
+ public ElectricalDevice.Output step(ElectricalDevice.Input input, double dt) {
+ electricalState = input;
+ if (isOn) {
+ double nominalCurrent = currentFunction.value(pneumaticState.pressure);
+ double adjustedCurrent = nominalCurrent * electricalState.voltage / NOMINAL_VOLTAGE;
+ return new ElectricalDevice.Output(adjustedCurrent);
+ } else {
+ return new ElectricalDevice.Output(0);
+ }
+ }
+
+ @Override
+ public PneumaticDevice.Output step(PneumaticDevice.Input input, double dt) {
+ pneumaticState = input;
+ double nominalFlowRate = flowRateFunction.value(pneumaticState.pressure);
+ double adjustedFlowRate = nominalFlowRate * electricalState.voltage / NOMINAL_VOLTAGE;
+ double flowVolume = adjustedFlowRate * dt;
+ return new PneumaticDevice.Output(flowVolume, 0);
+ }
+
+ @Override
+ public String name() {
+ return "AirCompressor";
+ }
}
diff --git a/src/main/java/com/team766/simulator/elements/AirReservoir.java b/src/main/java/com/team766/simulator/elements/AirReservoir.java
index a96ef54d..5d60b2c9 100644
--- a/src/main/java/com/team766/simulator/elements/AirReservoir.java
+++ b/src/main/java/com/team766/simulator/elements/AirReservoir.java
@@ -4,14 +4,14 @@
public class AirReservoir implements PneumaticDevice {
- private double volume;
+ private double volume;
- public AirReservoir(final double volume_) {
- this.volume = volume_;
- }
+ public AirReservoir(final double volume_) {
+ this.volume = volume_;
+ }
- @Override
- public PneumaticDevice.Output step(PneumaticDevice.Input input, double dt) {
- return new PneumaticDevice.Output(0, volume);
- }
+ @Override
+ public PneumaticDevice.Output step(PneumaticDevice.Input input, double dt) {
+ return new PneumaticDevice.Output(0, volume);
+ }
}
diff --git a/src/main/java/com/team766/simulator/elements/CanMotorController.java b/src/main/java/com/team766/simulator/elements/CanMotorController.java
index 00d8a99c..d424df78 100644
--- a/src/main/java/com/team766/simulator/elements/CanMotorController.java
+++ b/src/main/java/com/team766/simulator/elements/CanMotorController.java
@@ -5,24 +5,23 @@
public class CanMotorController extends MotorController {
- private int address;
+ private int address;
- public CanMotorController(final int address_, final ElectricalDevice downstream) {
- super(downstream);
- this.address = address_;
- }
+ public CanMotorController(final int address_, final ElectricalDevice downstream) {
+ super(downstream);
+ this.address = address_;
+ }
- @Override
- protected double getCommand() {
- return ProgramInterface.canMotorControllerChannels[address].command.output;
- }
+ @Override
+ protected double getCommand() {
+ return ProgramInterface.canMotorControllerChannels[address].command.output;
+ }
- public void setSensorPosition(double position) {
- ProgramInterface.canMotorControllerChannels[address].status.sensorPosition = position;
- }
-
- public void setSensorVelocity(double velocity) {
- ProgramInterface.canMotorControllerChannels[address].status.sensorVelocity = velocity;
- }
+ public void setSensorPosition(double position) {
+ ProgramInterface.canMotorControllerChannels[address].status.sensorPosition = position;
+ }
+ public void setSensorVelocity(double velocity) {
+ ProgramInterface.canMotorControllerChannels[address].status.sensorVelocity = velocity;
+ }
}
diff --git a/src/main/java/com/team766/simulator/elements/DCMotor.java b/src/main/java/com/team766/simulator/elements/DCMotor.java
index 98512ba3..08830762 100644
--- a/src/main/java/com/team766/simulator/elements/DCMotor.java
+++ b/src/main/java/com/team766/simulator/elements/DCMotor.java
@@ -4,65 +4,82 @@
import com.team766.simulator.interfaces.MechanicalAngularDevice;
public class DCMotor implements ElectricalDevice, MechanicalAngularDevice {
- // TODO: Add rotor inertia
- // TODO: Add thermal effects
-
- // Motor data from https://motors.vex.com/
- public static DCMotor makeCIM(String name) {
- return new DCMotor(name, 12, 5330, 2.7, 2.41, 131);
- }
- public static DCMotor make775Pro(String name) {
- return new DCMotor(name, 12, 18730, 0.7, 0.71, 134);
- }
- public static DCMotor makeFalcon500(String name) {
- return new DCMotor(name, 12, 6380, 1.5, 4.69, 257);
- }
- public static DCMotor makeNeo(String name) {
- return new DCMotor(name, 12, 5880, 1.3, 3.36, 166);
- }
- public static DCMotor makeNeo550(String name) {
- return new DCMotor(name, 12, 11710, 1.1, 1.08, 111);
- }
-
- // Motor velocity constant in radian/second/volt
- // (motor velocity) = kV * (motor voltage)
- private final double kV;
-
- // Motor torque constant in newton-meter/ampere
- // (motor torque) = kT * (motor current)
- private final double kT;
-
- // Motor resistance is calculated as 12V / (stall current at 12V)
- private final double motorResistance;
-
- private ElectricalDevice.Output electricalState = new ElectricalDevice.Output(0);
- private MechanicalAngularDevice.Input mechanicalState = new MechanicalAngularDevice.Input(0);
-
- private final String m_name;
-
- public DCMotor(String name, double referenceVoltage, double freeSpeedRpm, double freeCurrentAmps, double stallTorqueNewtonMeters, double stallCurrentAmps) {
- m_name = name;
-
- this.motorResistance = referenceVoltage / stallCurrentAmps;
- this.kV = freeSpeedRpm / 60.0 * 2 * Math.PI / (referenceVoltage - motorResistance * freeCurrentAmps);
- this.kT = stallTorqueNewtonMeters / stallCurrentAmps;
- }
-
- @Override
- public MechanicalAngularDevice.Output step(MechanicalAngularDevice.Input input, double dt) {
- mechanicalState = input;
-
- return new MechanicalAngularDevice.Output(kT * electricalState.current);
- }
-
- @Override
- public ElectricalDevice.Output step(ElectricalDevice.Input input, double dt) {
- electricalState = new ElectricalDevice.Output((input.voltage - mechanicalState.angularVelocity / kV) / motorResistance);
- return electricalState;
- }
-
- @Override
- public String name() {
- return m_name;
- }
+ // TODO: Add rotor inertia
+ // TODO: Add thermal effects
+
+ // Motor data from https://motors.vex.com/
+ public static DCMotor makeCIM(String name) {
+ return new DCMotor(name, 12, 5330, 2.7, 2.41, 131);
+ }
+
+ public static DCMotor make775Pro(String name) {
+ return new DCMotor(name, 12, 18730, 0.7, 0.71, 134);
+ }
+
+ public static DCMotor makeFalcon500(String name) {
+ return new DCMotor(name, 12, 6380, 1.5, 4.69, 257);
+ }
+
+ public static DCMotor makeNeo(String name) {
+ return new DCMotor(name, 12, 5880, 1.3, 3.36, 166);
+ }
+
+ public static DCMotor makeNeo550(String name) {
+ return new DCMotor(name, 12, 11710, 1.1, 1.08, 111);
+ }
+
+ // Motor velocity constant in radian/second/volt
+ // (motor velocity) = kV * (motor voltage)
+ private final double kV;
+
+ // Motor torque constant in newton-meter/ampere
+ // (motor torque) = kT * (motor current)
+ private final double kT;
+
+ // Motor resistance is calculated as 12V / (stall current at 12V)
+ private final double motorResistance;
+
+ private ElectricalDevice.Output electricalState = new ElectricalDevice.Output(0);
+ private MechanicalAngularDevice.Input mechanicalState = new MechanicalAngularDevice.Input(0);
+
+ private final String m_name;
+
+ public DCMotor(
+ String name,
+ double referenceVoltage,
+ double freeSpeedRpm,
+ double freeCurrentAmps,
+ double stallTorqueNewtonMeters,
+ double stallCurrentAmps) {
+ m_name = name;
+
+ this.motorResistance = referenceVoltage / stallCurrentAmps;
+ this.kV =
+ freeSpeedRpm
+ / 60.0
+ * 2
+ * Math.PI
+ / (referenceVoltage - motorResistance * freeCurrentAmps);
+ this.kT = stallTorqueNewtonMeters / stallCurrentAmps;
+ }
+
+ @Override
+ public MechanicalAngularDevice.Output step(MechanicalAngularDevice.Input input, double dt) {
+ mechanicalState = input;
+
+ return new MechanicalAngularDevice.Output(kT * electricalState.current);
+ }
+
+ @Override
+ public ElectricalDevice.Output step(ElectricalDevice.Input input, double dt) {
+ electricalState =
+ new ElectricalDevice.Output(
+ (input.voltage - mechanicalState.angularVelocity / kV) / motorResistance);
+ return electricalState;
+ }
+
+ @Override
+ public String name() {
+ return m_name;
+ }
}
diff --git a/src/main/java/com/team766/simulator/elements/DoubleActingPneumaticCylinder.java b/src/main/java/com/team766/simulator/elements/DoubleActingPneumaticCylinder.java
index 71fb7d4e..b85d5e39 100644
--- a/src/main/java/com/team766/simulator/elements/DoubleActingPneumaticCylinder.java
+++ b/src/main/java/com/team766/simulator/elements/DoubleActingPneumaticCylinder.java
@@ -1,55 +1,57 @@
package com.team766.simulator.elements;
-import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
-
import com.team766.simulator.PhysicalConstants;
import com.team766.simulator.interfaces.MechanicalDevice;
import com.team766.simulator.interfaces.PneumaticDevice;
+import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
public class DoubleActingPneumaticCylinder implements PneumaticDevice, MechanicalDevice {
- private static final Vector3D FORWARD = new Vector3D(1, 0, 0);
-
- private final double boreDiameter;
- private final double stroke;
-
- private boolean isExtended = false;
- private boolean commandExtended = false;
-
- private PneumaticDevice.Input pneumaticState = new PneumaticDevice.Input(0);
-
- public DoubleActingPneumaticCylinder(final double boreDiameter, final double stroke) {
- this.boreDiameter = boreDiameter;
- this.stroke = stroke;
- }
-
- public void setExtended(final boolean state) {
- commandExtended = state;
- }
-
- @Override
- public PneumaticDevice.Output step(PneumaticDevice.Input input, double dt) {
- pneumaticState = input;
- PneumaticDevice.Output output;
- double deviceVolume = boreArea() * stroke;
- if (commandExtended != isExtended) {
- output = new PneumaticDevice.Output(
- -deviceVolume * (input.pressure + PhysicalConstants.ATMOSPHERIC_PRESSURE)
- / PhysicalConstants.ATMOSPHERIC_PRESSURE,
- deviceVolume);
- } else {
- output = new PneumaticDevice.Output(0, deviceVolume);
- }
- isExtended = commandExtended;
- return output;
- }
-
- @Override
- public MechanicalDevice.Output step(MechanicalDevice.Input input, double dt) {
- Vector3D direction = isExtended ? FORWARD : FORWARD.negate();
- return new MechanicalDevice.Output(direction.scalarMultiply(boreArea() * pneumaticState.pressure));
- }
-
- private double boreArea() {
- return Math.PI * Math.pow(boreDiameter / 2., 2);
- }
+ private static final Vector3D FORWARD = new Vector3D(1, 0, 0);
+
+ private final double boreDiameter;
+ private final double stroke;
+
+ private boolean isExtended = false;
+ private boolean commandExtended = false;
+
+ private PneumaticDevice.Input pneumaticState = new PneumaticDevice.Input(0);
+
+ public DoubleActingPneumaticCylinder(final double boreDiameter, final double stroke) {
+ this.boreDiameter = boreDiameter;
+ this.stroke = stroke;
+ }
+
+ public void setExtended(final boolean state) {
+ commandExtended = state;
+ }
+
+ @Override
+ public PneumaticDevice.Output step(PneumaticDevice.Input input, double dt) {
+ pneumaticState = input;
+ PneumaticDevice.Output output;
+ double deviceVolume = boreArea() * stroke;
+ if (commandExtended != isExtended) {
+ output =
+ new PneumaticDevice.Output(
+ -deviceVolume
+ * (input.pressure + PhysicalConstants.ATMOSPHERIC_PRESSURE)
+ / PhysicalConstants.ATMOSPHERIC_PRESSURE,
+ deviceVolume);
+ } else {
+ output = new PneumaticDevice.Output(0, deviceVolume);
+ }
+ isExtended = commandExtended;
+ return output;
+ }
+
+ @Override
+ public MechanicalDevice.Output step(MechanicalDevice.Input input, double dt) {
+ Vector3D direction = isExtended ? FORWARD : FORWARD.negate();
+ return new MechanicalDevice.Output(
+ direction.scalarMultiply(boreArea() * pneumaticState.pressure));
+ }
+
+ private double boreArea() {
+ return Math.PI * Math.pow(boreDiameter / 2., 2);
+ }
}
diff --git a/src/main/java/com/team766/simulator/elements/ElectricalResistance.java b/src/main/java/com/team766/simulator/elements/ElectricalResistance.java
index 17edde92..879affc1 100644
--- a/src/main/java/com/team766/simulator/elements/ElectricalResistance.java
+++ b/src/main/java/com/team766/simulator/elements/ElectricalResistance.java
@@ -3,39 +3,44 @@
import com.team766.simulator.interfaces.ElectricalDevice;
public class ElectricalResistance implements ElectricalDevice {
- // Wire AWG -> Ohms/m (copper)
- // http://www.daycounter.com/Calculators/AWG.phtml
- private static double[] WIRE_RESISTANCE_PER_M = new double[] {
- 0.000323, 0.000407, 0.000513, 0.000647, 0.000815, 0.00103, 0.00130, 0.00163, 0.00206, 0.00260, // 0-9
- 0.00328, 0.00413, 0.00521, 0.00657, 0.00829, 0.0104, 0.0132, 0.0166, 0.0210, 0.0264, // 10-19
- 0.0333, 0.0420, 0.0530, 0.0668, 0.0842, 0.106, 0.134, 0.169, 0.213, 0.268, // 20-29
- 0.339, 0.427, 0.538, 0.679, 0.856, 1.08, 1.36, 1.72, 2.16, 2.73, 3.44, // 30-40
- };
- public static ElectricalResistance makeWires(final int awg, final double length,
- final ElectricalDevice downstream) {
- return new ElectricalResistance(WIRE_RESISTANCE_PER_M[awg] * length / 1000., downstream);
- }
-
- private final double resistance;
-
- private ElectricalDevice downstream;
-
- private ElectricalDevice.Output state;
-
- public ElectricalResistance(final double resistance_, final ElectricalDevice downstream_) {
- this.resistance = resistance_;
- this.downstream = downstream_;
- }
-
- @Override
- public ElectricalDevice.Output step(ElectricalDevice.Input input, double dt) {
- ElectricalDevice.Input downstreamInput = new ElectricalDevice.Input(input.voltage - resistance * state.current);
- state = downstream.step(downstreamInput, dt);
- return state;
- }
-
- @Override
- public String name() {
- return downstream.name() + "+resistance";
- }
+ // Wire AWG -> Ohms/m (copper)
+ // http://www.daycounter.com/Calculators/AWG.phtml
+ private static double[] WIRE_RESISTANCE_PER_M =
+ new double[] {
+ 0.000323, 0.000407, 0.000513, 0.000647, 0.000815, 0.00103, 0.00130, 0.00163,
+ 0.00206, 0.00260, // 0-9
+ 0.00328, 0.00413, 0.00521, 0.00657, 0.00829, 0.0104, 0.0132, 0.0166, 0.0210,
+ 0.0264, // 10-19
+ 0.0333, 0.0420, 0.0530, 0.0668, 0.0842, 0.106, 0.134, 0.169, 0.213, 0.268, // 20-29
+ 0.339, 0.427, 0.538, 0.679, 0.856, 1.08, 1.36, 1.72, 2.16, 2.73, 3.44, // 30-40
+ };
+
+ public static ElectricalResistance makeWires(
+ final int awg, final double length, final ElectricalDevice downstream) {
+ return new ElectricalResistance(WIRE_RESISTANCE_PER_M[awg] * length / 1000., downstream);
+ }
+
+ private final double resistance;
+
+ private ElectricalDevice downstream;
+
+ private ElectricalDevice.Output state;
+
+ public ElectricalResistance(final double resistance_, final ElectricalDevice downstream_) {
+ this.resistance = resistance_;
+ this.downstream = downstream_;
+ }
+
+ @Override
+ public ElectricalDevice.Output step(ElectricalDevice.Input input, double dt) {
+ ElectricalDevice.Input downstreamInput =
+ new ElectricalDevice.Input(input.voltage - resistance * state.current);
+ state = downstream.step(downstreamInput, dt);
+ return state;
+ }
+
+ @Override
+ public String name() {
+ return downstream.name() + "+resistance";
+ }
}
diff --git a/src/main/java/com/team766/simulator/elements/Gears.java b/src/main/java/com/team766/simulator/elements/Gears.java
index 51e9f969..48462b6d 100644
--- a/src/main/java/com/team766/simulator/elements/Gears.java
+++ b/src/main/java/com/team766/simulator/elements/Gears.java
@@ -3,24 +3,24 @@
import com.team766.simulator.interfaces.MechanicalAngularDevice;
public class Gears implements MechanicalAngularDevice {
- // TODO: Add rotational inertia
- // TODO: Add losses
+ // TODO: Add rotational inertia
+ // TODO: Add losses
- // Torque ratio (output / input)
- private final double torqueRatio;
+ // Torque ratio (output / input)
+ private final double torqueRatio;
- private MechanicalAngularDevice upstream;
+ private MechanicalAngularDevice upstream;
- public Gears(final double torqueRatio_, final MechanicalAngularDevice upstream_) {
- this.torqueRatio = torqueRatio_;
- this.upstream = upstream_;
- }
+ public Gears(final double torqueRatio_, final MechanicalAngularDevice upstream_) {
+ this.torqueRatio = torqueRatio_;
+ this.upstream = upstream_;
+ }
- @Override
- public MechanicalAngularDevice.Output step(MechanicalAngularDevice.Input input, double dt) {
- MechanicalAngularDevice.Input upstreamInput =
- new MechanicalAngularDevice.Input(input.angularVelocity * torqueRatio);
- MechanicalAngularDevice.Output upstreamOutput = upstream.step(upstreamInput, dt);
- return new MechanicalAngularDevice.Output(upstreamOutput.torque * torqueRatio);
- }
+ @Override
+ public MechanicalAngularDevice.Output step(MechanicalAngularDevice.Input input, double dt) {
+ MechanicalAngularDevice.Input upstreamInput =
+ new MechanicalAngularDevice.Input(input.angularVelocity * torqueRatio);
+ MechanicalAngularDevice.Output upstreamOutput = upstream.step(upstreamInput, dt);
+ return new MechanicalAngularDevice.Output(upstreamOutput.torque * torqueRatio);
+ }
}
diff --git a/src/main/java/com/team766/simulator/elements/MotorController.java b/src/main/java/com/team766/simulator/elements/MotorController.java
index 8889dda8..aa1d98ec 100644
--- a/src/main/java/com/team766/simulator/elements/MotorController.java
+++ b/src/main/java/com/team766/simulator/elements/MotorController.java
@@ -3,25 +3,26 @@
import com.team766.simulator.interfaces.ElectricalDevice;
public abstract class MotorController implements ElectricalDevice {
- private ElectricalDevice downstream;
+ private ElectricalDevice downstream;
- public MotorController(final ElectricalDevice downstream_) {
- this.downstream = downstream_;
- }
+ public MotorController(final ElectricalDevice downstream_) {
+ this.downstream = downstream_;
+ }
- // [-1, 1] representing the command sent from the application processor
- protected abstract double getCommand();
+ // [-1, 1] representing the command sent from the application processor
+ protected abstract double getCommand();
- @Override
- public ElectricalDevice.Output step(ElectricalDevice.Input input, double dt) {
- double command = getCommand();
- ElectricalDevice.Input downstreamInput = new ElectricalDevice.Input(input.voltage * command);
- ElectricalDevice.Output downstreamOutput = downstream.step(downstreamInput, dt);
- return new Output(Math.max(0, downstreamOutput.current * Math.signum(command)));
- }
+ @Override
+ public ElectricalDevice.Output step(ElectricalDevice.Input input, double dt) {
+ double command = getCommand();
+ ElectricalDevice.Input downstreamInput =
+ new ElectricalDevice.Input(input.voltage * command);
+ ElectricalDevice.Output downstreamOutput = downstream.step(downstreamInput, dt);
+ return new Output(Math.max(0, downstreamOutput.current * Math.signum(command)));
+ }
- @Override
- public String name() {
- return "MotorController:" + downstream.name();
- }
+ @Override
+ public String name() {
+ return "MotorController:" + downstream.name();
+ }
}
diff --git a/src/main/java/com/team766/simulator/elements/SingleActingPneumaticCylinder.java b/src/main/java/com/team766/simulator/elements/SingleActingPneumaticCylinder.java
index 5deb5c8e..23101267 100644
--- a/src/main/java/com/team766/simulator/elements/SingleActingPneumaticCylinder.java
+++ b/src/main/java/com/team766/simulator/elements/SingleActingPneumaticCylinder.java
@@ -1,59 +1,60 @@
package com.team766.simulator.elements;
-import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
-
import com.team766.simulator.PhysicalConstants;
import com.team766.simulator.interfaces.MechanicalDevice;
import com.team766.simulator.interfaces.PneumaticDevice;
+import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
public class SingleActingPneumaticCylinder implements PneumaticDevice, MechanicalDevice {
- private static final Vector3D FORWARD = new Vector3D(1, 0, 0);
-
- private final double boreDiameter;
- private final double stroke;
- private final double returnSpringForce;
-
- private boolean isExtended = false;
- private boolean commandExtended = false;
-
- private PneumaticDevice.Input pneumaticState = new PneumaticDevice.Input(0);
-
- public SingleActingPneumaticCylinder(final double boreDiameter_, final double stroke_,
- final double returnSpringForce_) {
- this.boreDiameter = boreDiameter_;
- this.stroke = stroke_;
- this.returnSpringForce = returnSpringForce_;
- }
-
- public void setExtended(final boolean state) {
- commandExtended = state;
- }
-
- @Override
- public PneumaticDevice.Output step(PneumaticDevice.Input input, double dt) {
- pneumaticState = input;
- PneumaticDevice.Output output;
- double deviceVolume = isExtended ? boreArea() * stroke : 0;
- if (isExtended && !commandExtended) {
- output = new PneumaticDevice.Output(
- -deviceVolume * (input.pressure + PhysicalConstants.ATMOSPHERIC_PRESSURE)
- / PhysicalConstants.ATMOSPHERIC_PRESSURE,
- deviceVolume);
- } else {
- output = new PneumaticDevice.Output(0, deviceVolume);
- }
- isExtended = commandExtended;
- return output;
- }
-
- @Override
- public MechanicalDevice.Output step(MechanicalDevice.Input input, double dt) {
- Vector3D direction = isExtended ? FORWARD : FORWARD.negate();
- double force = isExtended ? boreArea() * pneumaticState.pressure : returnSpringForce;
- return new MechanicalDevice.Output(direction.scalarMultiply(force));
- }
-
- private double boreArea() {
- return Math.PI * Math.pow(boreDiameter / 2., 2);
- }
+ private static final Vector3D FORWARD = new Vector3D(1, 0, 0);
+
+ private final double boreDiameter;
+ private final double stroke;
+ private final double returnSpringForce;
+
+ private boolean isExtended = false;
+ private boolean commandExtended = false;
+
+ private PneumaticDevice.Input pneumaticState = new PneumaticDevice.Input(0);
+
+ public SingleActingPneumaticCylinder(
+ final double boreDiameter_, final double stroke_, final double returnSpringForce_) {
+ this.boreDiameter = boreDiameter_;
+ this.stroke = stroke_;
+ this.returnSpringForce = returnSpringForce_;
+ }
+
+ public void setExtended(final boolean state) {
+ commandExtended = state;
+ }
+
+ @Override
+ public PneumaticDevice.Output step(PneumaticDevice.Input input, double dt) {
+ pneumaticState = input;
+ PneumaticDevice.Output output;
+ double deviceVolume = isExtended ? boreArea() * stroke : 0;
+ if (isExtended && !commandExtended) {
+ output =
+ new PneumaticDevice.Output(
+ -deviceVolume
+ * (input.pressure + PhysicalConstants.ATMOSPHERIC_PRESSURE)
+ / PhysicalConstants.ATMOSPHERIC_PRESSURE,
+ deviceVolume);
+ } else {
+ output = new PneumaticDevice.Output(0, deviceVolume);
+ }
+ isExtended = commandExtended;
+ return output;
+ }
+
+ @Override
+ public MechanicalDevice.Output step(MechanicalDevice.Input input, double dt) {
+ Vector3D direction = isExtended ? FORWARD : FORWARD.negate();
+ double force = isExtended ? boreArea() * pneumaticState.pressure : returnSpringForce;
+ return new MechanicalDevice.Output(direction.scalarMultiply(force));
+ }
+
+ private double boreArea() {
+ return Math.PI * Math.pow(boreDiameter / 2., 2);
+ }
}
diff --git a/src/main/java/com/team766/simulator/elements/Wheel.java b/src/main/java/com/team766/simulator/elements/Wheel.java
index 4ae82857..432c9fbc 100644
--- a/src/main/java/com/team766/simulator/elements/Wheel.java
+++ b/src/main/java/com/team766/simulator/elements/Wheel.java
@@ -1,40 +1,44 @@
package com.team766.simulator.elements;
-import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
-
import com.team766.simulator.Parameters;
import com.team766.simulator.PhysicalConstants;
import com.team766.simulator.interfaces.MechanicalAngularDevice;
import com.team766.simulator.interfaces.MechanicalDevice;
+import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
// Simulate a wheel revolving around the positive Y axis.
public class Wheel implements MechanicalDevice {
- // TODO: Add transverse friction
- // TODO: Add traction limit/wheel slip (static vs kinetic friction)
- // TODO: Add rotational inertia
-
- private static final Vector3D FORWARD = new Vector3D(-1, 0, 0);
-
- // Diameter of the wheel in meters
- private final double diameter;
-
- private MechanicalAngularDevice upstream;
-
- public Wheel(final double diameter_, final MechanicalAngularDevice upstream_) {
- this.diameter = diameter_;
- this.upstream = upstream_;
- }
-
- @Override
- public MechanicalDevice.Output step(MechanicalDevice.Input input, double dt) {
- MechanicalAngularDevice.Input upstreamInput =
- new MechanicalAngularDevice.Input(FORWARD.dotProduct(input.velocity) * 2. / diameter);
- MechanicalAngularDevice.Output upstreamOutput = upstream.step(upstreamInput, dt);
- double appliedForce = upstreamOutput.torque * 2. / diameter;
- double maxFriction = Parameters.WHEEL_COEFFICIENT_OF_FRICTION * Parameters.ROBOT_MASS * PhysicalConstants.GRAVITY_ACCELERATION / Parameters.NUM_LOADED_WHEELS;
- if (Math.abs(appliedForce) > maxFriction) {
- appliedForce = Math.signum(appliedForce) * maxFriction;
- }
- return new MechanicalDevice.Output(FORWARD.scalarMultiply(appliedForce));
- }
+ // TODO: Add transverse friction
+ // TODO: Add traction limit/wheel slip (static vs kinetic friction)
+ // TODO: Add rotational inertia
+
+ private static final Vector3D FORWARD = new Vector3D(-1, 0, 0);
+
+ // Diameter of the wheel in meters
+ private final double diameter;
+
+ private MechanicalAngularDevice upstream;
+
+ public Wheel(final double diameter_, final MechanicalAngularDevice upstream_) {
+ this.diameter = diameter_;
+ this.upstream = upstream_;
+ }
+
+ @Override
+ public MechanicalDevice.Output step(MechanicalDevice.Input input, double dt) {
+ MechanicalAngularDevice.Input upstreamInput =
+ new MechanicalAngularDevice.Input(
+ FORWARD.dotProduct(input.velocity) * 2. / diameter);
+ MechanicalAngularDevice.Output upstreamOutput = upstream.step(upstreamInput, dt);
+ double appliedForce = upstreamOutput.torque * 2. / diameter;
+ double maxFriction =
+ Parameters.WHEEL_COEFFICIENT_OF_FRICTION
+ * Parameters.ROBOT_MASS
+ * PhysicalConstants.GRAVITY_ACCELERATION
+ / Parameters.NUM_LOADED_WHEELS;
+ if (Math.abs(appliedForce) > maxFriction) {
+ appliedForce = Math.signum(appliedForce) * maxFriction;
+ }
+ return new MechanicalDevice.Output(FORWARD.scalarMultiply(appliedForce));
+ }
}
diff --git a/src/main/java/com/team766/simulator/interfaces/ElectricalDevice.java b/src/main/java/com/team766/simulator/interfaces/ElectricalDevice.java
index dabf49e1..3fa0c9f0 100644
--- a/src/main/java/com/team766/simulator/interfaces/ElectricalDevice.java
+++ b/src/main/java/com/team766/simulator/interfaces/ElectricalDevice.java
@@ -1,31 +1,31 @@
package com.team766.simulator.interfaces;
public interface ElectricalDevice {
- class Input {
- public Input(final double voltage_) {
- this.voltage = voltage_;
- }
+ class Input {
+ public Input(final double voltage_) {
+ this.voltage = voltage_;
+ }
- public Input(final Input other) {
- voltage = other.voltage;
- }
+ public Input(final Input other) {
+ voltage = other.voltage;
+ }
- public final double voltage;
- }
+ public final double voltage;
+ }
- class Output {
- public Output(final double current_) {
- this.current = current_;
- }
+ class Output {
+ public Output(final double current_) {
+ this.current = current_;
+ }
- public Output(final Output other) {
- current = other.current;
- }
+ public Output(final Output other) {
+ current = other.current;
+ }
- public final double current;
- }
+ public final double current;
+ }
- public String name();
-
- public Output step(Input input, double dt);
+ public String name();
+
+ public Output step(Input input, double dt);
}
diff --git a/src/main/java/com/team766/simulator/interfaces/MechanicalAngularDevice.java b/src/main/java/com/team766/simulator/interfaces/MechanicalAngularDevice.java
index d2f6a150..fbe0acbb 100644
--- a/src/main/java/com/team766/simulator/interfaces/MechanicalAngularDevice.java
+++ b/src/main/java/com/team766/simulator/interfaces/MechanicalAngularDevice.java
@@ -1,29 +1,29 @@
package com.team766.simulator.interfaces;
public interface MechanicalAngularDevice {
- class Input {
- public Input(final double angularVelocity_) {
- this.angularVelocity = angularVelocity_;
- }
+ class Input {
+ public Input(final double angularVelocity_) {
+ this.angularVelocity = angularVelocity_;
+ }
- public Input(final Input other) {
- this.angularVelocity = other.angularVelocity;
- }
+ public Input(final Input other) {
+ this.angularVelocity = other.angularVelocity;
+ }
- public final double angularVelocity;
- }
+ public final double angularVelocity;
+ }
- class Output {
- public Output(final double torque_) {
- this.torque = torque_;
- }
+ class Output {
+ public Output(final double torque_) {
+ this.torque = torque_;
+ }
- public Output(final Output other) {
- this.torque = other.torque;
- }
+ public Output(final Output other) {
+ this.torque = other.torque;
+ }
- public final double torque;
- }
-
- public Output step(Input input, double dt);
+ public final double torque;
+ }
+
+ public Output step(Input input, double dt);
}
diff --git a/src/main/java/com/team766/simulator/interfaces/MechanicalDevice.java b/src/main/java/com/team766/simulator/interfaces/MechanicalDevice.java
index 85b375f7..8388761d 100644
--- a/src/main/java/com/team766/simulator/interfaces/MechanicalDevice.java
+++ b/src/main/java/com/team766/simulator/interfaces/MechanicalDevice.java
@@ -3,32 +3,32 @@
import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
public interface MechanicalDevice {
- class Input {
- public Input(final Vector3D position_, final Vector3D velocity_) {
- this.position = position_;
- this.velocity = velocity_;
- }
-
- public Input(final Input other) {
- position = other.position;
- velocity = other.velocity;
- }
-
- public final Vector3D position;
- public final Vector3D velocity;
- }
-
- class Output {
- public Output(final Vector3D force_) {
- this.force = force_;
- }
-
- public Output(final Output other) {
- force = other.force;
- }
-
- public final Vector3D force;
- }
-
- public Output step(Input input, double dt);
+ class Input {
+ public Input(final Vector3D position_, final Vector3D velocity_) {
+ this.position = position_;
+ this.velocity = velocity_;
+ }
+
+ public Input(final Input other) {
+ position = other.position;
+ velocity = other.velocity;
+ }
+
+ public final Vector3D position;
+ public final Vector3D velocity;
+ }
+
+ class Output {
+ public Output(final Vector3D force_) {
+ this.force = force_;
+ }
+
+ public Output(final Output other) {
+ force = other.force;
+ }
+
+ public final Vector3D force;
+ }
+
+ public Output step(Input input, double dt);
}
diff --git a/src/main/java/com/team766/simulator/interfaces/PneumaticDevice.java b/src/main/java/com/team766/simulator/interfaces/PneumaticDevice.java
index fef20273..4b4ffd30 100644
--- a/src/main/java/com/team766/simulator/interfaces/PneumaticDevice.java
+++ b/src/main/java/com/team766/simulator/interfaces/PneumaticDevice.java
@@ -1,42 +1,42 @@
package com.team766.simulator.interfaces;
public interface PneumaticDevice {
- class Input {
- public Input(final double pressure_) {
- this.pressure = pressure_;
- }
-
- public Input(final Input other) {
- pressure = other.pressure;
- }
-
- // Pascals (relative pressure)
- public final double pressure;
- }
-
- class Output {
- public Output(final double flowVolume_, final double deviceVolume_) {
- this.flowVolume = flowVolume_;
- this.deviceVolume = deviceVolume_;
- }
-
- public Output(final Output other) {
- flowVolume = other.flowVolume;
- deviceVolume = other.deviceVolume;
- }
-
- // Volumetric flow (delta m^3 at atmospheric pressure)
- // Positive flow is into the system, e.g. from a compressor
- // Negative flow is out of the system, e.g. from venting to atmosphere
- public final double flowVolume;
-
- // Volume of air that the device contains (m^3)
- public final double deviceVolume;
-
- // Note that an expanding volume (such as a cylinder expanding)
- // should increase volume, but have 0 flow volume because no
- // pressurized air is actually leaving the system.
- }
-
- public Output step(Input input, double dt);
+ class Input {
+ public Input(final double pressure_) {
+ this.pressure = pressure_;
+ }
+
+ public Input(final Input other) {
+ pressure = other.pressure;
+ }
+
+ // Pascals (relative pressure)
+ public final double pressure;
+ }
+
+ class Output {
+ public Output(final double flowVolume_, final double deviceVolume_) {
+ this.flowVolume = flowVolume_;
+ this.deviceVolume = deviceVolume_;
+ }
+
+ public Output(final Output other) {
+ flowVolume = other.flowVolume;
+ deviceVolume = other.deviceVolume;
+ }
+
+ // Volumetric flow (delta m^3 at atmospheric pressure)
+ // Positive flow is into the system, e.g. from a compressor
+ // Negative flow is out of the system, e.g. from venting to atmosphere
+ public final double flowVolume;
+
+ // Volume of air that the device contains (m^3)
+ public final double deviceVolume;
+
+ // Note that an expanding volume (such as a cylinder expanding)
+ // should increase volume, but have 0 flow volume because no
+ // pressurized air is actually leaving the system.
+ }
+
+ public Output step(Input input, double dt);
}
diff --git a/src/main/java/com/team766/simulator/mechanisms/DoubleJointedArm.java b/src/main/java/com/team766/simulator/mechanisms/DoubleJointedArm.java
index dad28752..d36ddc51 100644
--- a/src/main/java/com/team766/simulator/mechanisms/DoubleJointedArm.java
+++ b/src/main/java/com/team766/simulator/mechanisms/DoubleJointedArm.java
@@ -16,280 +16,352 @@
* "virtual linkage".
*/
public class DoubleJointedArm {
- // General nomenclature:
- // J1 refers to the first (shoulder) joint, as well as the first link of the arm.
- // J2 refers to the second (elbow) joint, as well as the second link of the arm.
-
- private static final double J1_INITIAL_ANGLE = Math.toRadians(202.); // radians, relative to "down"
- private static final double J2_INITIAL_ANGLE = Math.toRadians(32.); // radians, relative to "down"
-
- // Joint limits
- private static final double J1_MIN = Math.toRadians(90.); // radians, relative to "down"
- private static final double J1_MAX = Math.toRadians(205.); // radians, relative to "down"
- private static final double J2_MIN = Math.toRadians(-170); // radians, relative to J1
- private static final double J2_MAX = Math.toRadians(170); // radians, relative to J1
-
- // Physical characterisics of the arm links.
- // NOTE(rcahoon, 2023-02-09): We could determine these from CAD data, but using guessed/approximate values for now.
- private static final double J1_LENGTH = 1; // m
- private static final double J2_LENGTH = 1; // m
- private static final double J1_MASS = 2; // kg
- private static final double J2_MASS = 5; // kg
- private static final double J1_FIRST_MOMENT_OF_MASS = J1_MASS * J1_LENGTH; // kg m, with the origin/axis at the first joint
- private static final double J1_SECOND_MOMENT_OF_MASS = J1_MASS * J1_LENGTH * J1_LENGTH; // kg m^2, with the origin/axis at the first joint - aka moment of inertia
- private static final double J2_FIRST_MOMENT_OF_MASS = J2_MASS * J2_LENGTH; // kg m, with the origin/axis at the second joint
- private static final double J2_SECOND_MOMENT_OF_MASS = J2_MASS * J2_LENGTH * J2_LENGTH; // kg m^2, with the origin/axis at the second joint - aka moment of inertia
-
- private static final double J1_GEAR_RATIO = 4. * 4. * 3. * (58. / 14.);
- private static final double J2_GEAR_RATIO = 4. * 4. * 3. * (58. / 14.);
-
- // Simulated CAN Bus IDs for the two motors.
- private static final int J1_CAN_CHANNEL = 98;
- private static final int J2_CAN_CHANNEL = 99;
-
- private DCMotor j1Motor = DCMotor.makeNeo("ArmJoint1");
- private DCMotor j2Motor = DCMotor.makeNeo("ArmJoint2");
-
- private CanMotorController j1Controller = new CanMotorController(J1_CAN_CHANNEL, j1Motor);
- private CanMotorController j2Controller = new CanMotorController(J2_CAN_CHANNEL, j2Motor);
-
- private Gears j1Gears = new Gears(J1_GEAR_RATIO, j1Motor);
- private Gears j2Gears = new Gears(J2_GEAR_RATIO, j2Motor);
-
- private Matrix state = VecBuilder.fill(
- J1_INITIAL_ANGLE, // j1 angle, radians, relative to "down"
- J2_INITIAL_ANGLE, // j2 angle, radians, relative to "down"
- 0, // j1 velocity, radians/sec
- 0 // j2 velocity, radians/sec
- );
-
- // Derivation of the equations of motion via Lagrangian mechanics.
- // This system is often referred to as a "double pendulum", and is a commonly studied problem,
- // so literature is available. See this series of videos for an explanation:
- // https://youtu.be/tc2ah-KnDXw, https://youtu.be/eBg8gof1RBg, https://youtu.be/QE1_H2vtHLU
- // The formulation given below is slightly more generalized to allow arbitrary mass
- // distributions along the arm links.
- //
- // Kinematics:
- // x1 = J1_FIRST_MOMENT_OF_MASS / J1_MASS * sin(theta1)
- // y1 = -J1_FIRST_MOMENT_OF_MASS / J1_MASS * cos(theta1)
- // x2 = J1_LENGTH * sin(theta1) + J2_FIRST_MOMENT_OF_MASS / J2_MASS * sin(theta2)
- // y2 = -J1_LENGTH * cos(theta1) - J2_FIRST_MOMENT_OF_MASS / J2_MASS * cos(theta2)
- //
- // Lagrangian:
- // L = T - V
- // = 1/2 * J1_MASS * (x1'^2 + y1'^2)
- // + 1/2 * (J1_SECOND_MOMENT_OF_MASS - J1_FIRST_MOMENT_OF_MASS^2 / J1_MASS) * theta1'^2
- // + 1/2 * J2_MASS * (x2'^2 + y2'^2)
- // + 1/2 * (J2_SECOND_MOMENT_OF_MASS - J2_FIRST_MOMENT_OF_MASS^2 / J2_MASS) * theta2'^2
- // - J1_MASS * y1 * PhysicalConstants.GRAVITY_ACCELERATION
- // - J2_MASS * y2 * PhysicalConstants.GRAVITY_ACCELERATION
- // = 1/2 * J1_SECOND_MOMENT_OF_MASS * theta1'^2
- // + 1/2 * J2_MASS * ((J1_LENGTH * cos(theta1) * theta1' + J2_FIRST_MOMENT_OF_MASS / J2_MASS * cos(theta2) * theta2')^2 + (J1_LENGTH * sin(theta1) * theta1' + J2_FIRST_MOMENT_OF_MASS / J2_MASS * sin(theta2) * theta2')^2)
- // + 1/2 * (J2_SECOND_MOMENT_OF_MASS - J2_FIRST_MOMENT_OF_MASS^2 / J2_MASS) * theta2'^2
- // + J1_FIRST_MOMENT_OF_MASS * cos(theta1) * PhysicalConstants.GRAVITY_ACCELERATION
- // + (J2_MASS * J1_LENGTH * cos(theta1) + J2_FIRST_MOMENT_OF_MASS * cos(theta2)) * PhysicalConstants.GRAVITY_ACCELERATION
- // = 1/2 * J1_SECOND_MOMENT_OF_MASS * theta1'^2
- // + 1/2 * (J2_MASS * J1_LENGTH^2 * theta1'^2 + 2 * J1_LENGTH * cos(theta1 - theta2) * theta1' * J2_FIRST_MOMENT_OF_MASS * theta2' + J2_FIRST_MOMENT_OF_MASS^2 / J2_MASS * theta2'^2)
- // + 1/2 * (J2_SECOND_MOMENT_OF_MASS - J2_FIRST_MOMENT_OF_MASS^2 / J2_MASS) * theta2'^2
- // + J1_FIRST_MOMENT_OF_MASS * cos(theta1) * PhysicalConstants.GRAVITY_ACCELERATION
- // + (J2_MASS * J1_LENGTH * cos(theta1) + J2_FIRST_MOMENT_OF_MASS * cos(theta2)) * PhysicalConstants.GRAVITY_ACCELERATION
- // = 1/2 * (J1_SECOND_MOMENT_OF_MASS + J2_MASS * J1_LENGTH^2) * theta1'^2
- // + 1/2 * J2_SECOND_MOMENT_OF_MASS * theta2'^2
- // + J2_FIRST_MOMENT_OF_MASS * J1_LENGTH * cos(theta1 - theta2) * theta1' * theta2'
- // + (J1_FIRST_MOMENT_OF_MASS + J2_MASS * J1_LENGTH) * PhysicalConstants.GRAVITY_ACCELERATION * cos(theta1)
- // + J2_FIRST_MOMENT_OF_MASS * PhysicalConstants.GRAVITY_ACCELERATION * cos(theta2)
- //
- // Define some constants to simplify the following equations
- // I1 = J1_SECOND_MOMENT_OF_MASS + J2_MASS * J1_LENGTH^2
- // I2 = J2_SECOND_MOMENT_OF_MASS
- // Ix = J2_FIRST_MOMENT_OF_MASS * J1_LENGTH
- // g1 = (J1_FIRST_MOMENT_OF_MASS + J2_MASS * J1_LENGTH) * PhysicalConstants.GRAVITY_ACCELERATION
- // g2 = J2_FIRST_MOMENT_OF_MASS * PhysicalConstants.GRAVITY_ACCELERATION
- //
- // L = 1/2 * I1 * theta1'^2 + 1/2 * I2 * theta2'^2 + Ix * cos(theta1 - theta2) * theta1' * theta2' + g1 * cos(theta1) + g2 * cos(theta2)
- //
- // Euler-Lagrangian:
- // d/dt [Partial]L/[Partial]theta' - [Partial]L/[Partial]theta = tau
- //
- // d/dt [Partial]L/[Partial]theta1' - [Partial]L/[Partial]theta1 = I1 * theta1'' + Ix * cos(theta1 - theta2) * theta2'' - Ix * sin(theta1 - theta2) * (theta1' - theta2') * theta2'
- // + Ix * sin(theta1 - theta2) * theta1' * theta2' + g1 * sin(theta1)
- // = I1 * theta1'' + Ix * cos(theta1 - theta2) * theta2'' + Ix * sin(theta1 - theta2) * theta2'^2 + g1 * sin(theta1)
- // = tau1
- //
- // d/dt [Partial]L/[Partial]theta2' - [Partial]L/[Partial]theta2 = I2 * theta2'' + Ix * cos(theta1 - theta2) * theta1'' - Ix * sin(theta1 - theta2) * (theta1' - theta2') * theta1'
- // - Ix * sin(theta1 - theta2) * theta1' * theta2' + g2 * sin(theta2))
- // = I2 * theta2'' + Ix * cos(theta1 - theta2) * theta1'' - Ix * sin(theta1 - theta2) * theta1'^2 + g2 * sin(theta2)
- // = tau2
- //
- // Rewrite as a matrix equation:
- // M * theta'' + G = tau
- // theta'' = M^-1 * (tau - G)
- //
- // M = [ I1, Ix * cos(theta1 - theta2) ]
- // = [ Ix * cos(theta1 - theta2), I2 ]
- // G = [ Ix * sin(theta1 - theta2) * theta1' * theta2' + g1 * sin(theta1) ]
- // [ -Ix * sin(theta1 - theta2) * theta1' * theta2' + g2 * sin(theta2) ]
-
- public DoubleJointedArm(ElectricalSystem electricalSystem) {
- electricalSystem.addDevice(j1Controller);
- electricalSystem.addDevice(j2Controller);
- }
-
- /**
- * Advance the simulation model by the given amount of time.
- */
- public void step(double dt) {
- final MechanicalAngularDevice.Output j1Drive = j1Gears.step(new MechanicalAngularDevice.Input(getJ1Velocity()), dt);
- final MechanicalAngularDevice.Output j2Drive = j2Gears.step(new MechanicalAngularDevice.Input(getJ2Velocity()), dt);
-
- double j1Limit = 0.;
- if (state.get(0, 0) <= J1_MIN) {
- state.set(0, 0, J1_MIN);
- state.set(2, 0, 0.0);
- j1Limit = 1.;
- }
- if (state.get(0, 0) >= J1_MAX) {
- state.set(0, 0, J1_MAX);
- state.set(2, 0, 0.0);
- j1Limit = -1.;
- }
- double j2Limit = 0.;
- if (state.get(1, 0) - state.get(0, 0) <= J2_MIN) {
- state.set(1, 0, J2_MIN + state.get(0, 0));
- j2Limit = 1.;
- }
- if (state.get(1, 0) - state.get(0, 0) >= J2_MAX) {
- state.set(1, 0, J2_MAX + state.get(0, 0));
- j2Limit = -1.;
- }
-
- if (state.get(2, 0) * j1Limit < 0) {
- state.set(2, 0, 0.0);
- }
- if (state.get(3, 0) * j2Limit < 0) {
- state.set(3, 0, 0.0);
- }
-
- final var u = VecBuilder.fill(j1Drive.torque, j2Drive.torque, j1Limit, j2Limit);
- final var stateDot = S(state, u);
- state = state.plus(stateDot.times(dt));
-
- j1Controller.setSensorPosition(state.get(0, 0));
- j2Controller.setSensorPosition(state.get(1, 0));
- j1Controller.setSensorVelocity(state.get(2, 0));
- j2Controller.setSensorVelocity(state.get(3, 0));
- }
-
- /**
- * State space model X'(t) = S(X(t), u(t)) of the system
- */
- // Defined as a static method to make sure we don't accidentally refer to any of the state
- // stored in class member variables when we should be referring to the state the integrator
- // gave us.
- private static Vector S(Matrix state, Matrix u) {
- Vector motorTorques = VecBuilder.fill(u.get(0, 0), u.get(1, 0));
- Vector jointLimits = VecBuilder.fill(u.get(2, 0), u.get(3, 0));
- Vector velocity = VecBuilder.fill(state.get(2, 0), state.get(3, 0));
-
- final double Mx = J2_FIRST_MOMENT_OF_MASS * J1_LENGTH * Math.cos(state.get(0, 0) - state.get(1, 0));
- var M = Matrix.mat(N2.instance, N2.instance).fill(
- J1_SECOND_MOMENT_OF_MASS + J2_MASS * J1_LENGTH * J1_LENGTH, Mx,
- Mx, J2_SECOND_MOMENT_OF_MASS
- );
-
- final double C = J2_FIRST_MOMENT_OF_MASS * J1_LENGTH * Math.sin(state.get(0, 0) - state.get(1, 0)) * state.get(2, 0) * state.get(3, 0);
- final var G = VecBuilder.fill(
- C + (J1_FIRST_MOMENT_OF_MASS + J2_MASS * J1_LENGTH) * PhysicalConstants.GRAVITY_ACCELERATION * Math.sin(state.get(0, 0)),
- -C + J2_FIRST_MOMENT_OF_MASS * PhysicalConstants.GRAVITY_ACCELERATION * Math.sin(state.get(1, 0))
- );
- var T = motorTorques.minus(G);
-
- if (T.get(0, 0) * jointLimits.get(0, 0) < 0) {
- M.set(0, 0, 1.0);
- M.set(1, 0, 0.0);
- M.set(0, 1, 0.0);
- T.set(0, 0, 0.0);
- }
- if (T.get(1, 0) * jointLimits.get(1, 0) < 0) {
- M.set(1, 1, 1.0);
- M.set(1, 0, 0.0);
- M.set(0, 1, 0.0);
- T.set(1, 0, 0.0);
- }
-
- final var acceleration = M.inv().times(T);
-
- return VecBuilder.fill(velocity.get(0, 0), velocity.get(1, 0), acceleration.get(0, 0), acceleration.get(1, 0));
- }
-
- /**
- * Return the angle of the first joint.
- */
- public double getJ1Angle() {
- return state.get(0, 0);
- }
-
- /**
- * Return the angle of the second joint.
- */
- public double getJ2Angle() {
- return state.get(1, 0);
- }
-
- /**
- * Return the velocity of the first joint.
- */
- public double getJ1Velocity() {
- return state.get(2, 0);
- }
-
- /**
- * Return the velocity of the second joint.
- */
- public double getJ2Velocity() {
- return state.get(3, 0);
- }
-
- /**
- * Calculate the total kinetic and potential energy in the arm.
- *
- * If no energy is added or removed (e.g. by motors or friction), this quantity should remain
- * constant (i.e. conservation of energy), so plotting it over time makes a good sanity check of
- * the model.
- */
- public double getEnergy() {
- double y1 = -J1_FIRST_MOMENT_OF_MASS / J1_MASS * Math.cos(getJ1Angle());
- double y2 = -J1_LENGTH * Math.cos(getJ1Angle()) - J2_FIRST_MOMENT_OF_MASS / J2_MASS * Math.cos(getJ2Angle());
- double dx1 = J1_FIRST_MOMENT_OF_MASS / J1_MASS * Math.cos(getJ1Angle()) * getJ1Velocity();
- double dy1 = J1_FIRST_MOMENT_OF_MASS / J1_MASS * Math.sin(getJ1Angle()) * getJ1Velocity();
- double dx2 = J1_LENGTH * Math.cos(getJ1Angle()) * getJ1Velocity() + J2_FIRST_MOMENT_OF_MASS / J2_MASS * Math.cos(getJ2Angle()) * getJ2Velocity();
- double dy2 = J1_LENGTH * Math.sin(getJ1Angle()) * getJ1Velocity() + J2_FIRST_MOMENT_OF_MASS / J2_MASS * Math.sin(getJ2Angle()) * getJ2Velocity();
-
- double energy = 0.5 * J1_MASS * (dx1*dx1 + dy1*dy1)
- + 0.5 * (J1_SECOND_MOMENT_OF_MASS - J1_FIRST_MOMENT_OF_MASS * J1_FIRST_MOMENT_OF_MASS / J1_MASS) * getJ1Velocity() * getJ1Velocity()
- + 0.5 * J2_MASS * (dx2*dx2 + dy2*dy2)
- + 0.5 * (J2_SECOND_MOMENT_OF_MASS - J2_FIRST_MOMENT_OF_MASS * J2_FIRST_MOMENT_OF_MASS / J2_MASS) * getJ2Velocity() * getJ2Velocity()
- + J1_MASS * y1 * PhysicalConstants.GRAVITY_ACCELERATION
- + J2_MASS * y2 * PhysicalConstants.GRAVITY_ACCELERATION;
- return energy;
- }
-
- /**
- * Calculuate the cartesian coordinates of the end of the first link.
- */
- public Vector getJ1Position() {
- double x1 = J1_LENGTH * Math.sin(getJ1Angle());
- double y1 = -J1_LENGTH * Math.cos(getJ1Angle());
- return VecBuilder.fill(x1, y1);
- }
-
- /**
- * Calculuate the cartesian coordinates of the end of the second link.
- */
- public Vector getJ2Position() {
- double x2 = J1_LENGTH * Math.sin(getJ1Angle()) + J2_LENGTH * Math.sin(getJ2Angle());
- double y2 = -J1_LENGTH * Math.cos(getJ1Angle()) - J2_LENGTH * Math.cos(getJ2Angle());
- return VecBuilder.fill(x2, y2);
- }
+ // General nomenclature:
+ // J1 refers to the first (shoulder) joint, as well as the first link of the arm.
+ // J2 refers to the second (elbow) joint, as well as the second link of the arm.
+
+ private static final double J1_INITIAL_ANGLE =
+ Math.toRadians(202.); // radians, relative to "down"
+ private static final double J2_INITIAL_ANGLE =
+ Math.toRadians(32.); // radians, relative to "down"
+
+ // Joint limits
+ private static final double J1_MIN = Math.toRadians(90.); // radians, relative to "down"
+ private static final double J1_MAX = Math.toRadians(205.); // radians, relative to "down"
+ private static final double J2_MIN = Math.toRadians(-170); // radians, relative to J1
+ private static final double J2_MAX = Math.toRadians(170); // radians, relative to J1
+
+ // Physical characterisics of the arm links.
+ // NOTE(rcahoon, 2023-02-09): We could determine these from CAD data, but using
+ // guessed/approximate values for now.
+ private static final double J1_LENGTH = 1; // m
+ private static final double J2_LENGTH = 1; // m
+ private static final double J1_MASS = 2; // kg
+ private static final double J2_MASS = 5; // kg
+ private static final double J1_FIRST_MOMENT_OF_MASS =
+ J1_MASS * J1_LENGTH; // kg m, with the origin/axis at the first joint
+ private static final double J1_SECOND_MOMENT_OF_MASS =
+ J1_MASS * J1_LENGTH
+ * J1_LENGTH; // kg m^2, with the origin/axis at the first joint - aka moment of
+ // inertia
+ private static final double J2_FIRST_MOMENT_OF_MASS =
+ J2_MASS * J2_LENGTH; // kg m, with the origin/axis at the second joint
+ private static final double J2_SECOND_MOMENT_OF_MASS =
+ J2_MASS * J2_LENGTH
+ * J2_LENGTH; // kg m^2, with the origin/axis at the second joint - aka moment of
+ // inertia
+
+ private static final double J1_GEAR_RATIO = 4. * 4. * 3. * (58. / 14.);
+ private static final double J2_GEAR_RATIO = 4. * 4. * 3. * (58. / 14.);
+
+ // Simulated CAN Bus IDs for the two motors.
+ private static final int J1_CAN_CHANNEL = 98;
+ private static final int J2_CAN_CHANNEL = 99;
+
+ private DCMotor j1Motor = DCMotor.makeNeo("ArmJoint1");
+ private DCMotor j2Motor = DCMotor.makeNeo("ArmJoint2");
+
+ private CanMotorController j1Controller = new CanMotorController(J1_CAN_CHANNEL, j1Motor);
+ private CanMotorController j2Controller = new CanMotorController(J2_CAN_CHANNEL, j2Motor);
+
+ private Gears j1Gears = new Gears(J1_GEAR_RATIO, j1Motor);
+ private Gears j2Gears = new Gears(J2_GEAR_RATIO, j2Motor);
+
+ private Matrix state =
+ VecBuilder.fill(
+ J1_INITIAL_ANGLE, // j1 angle, radians, relative to "down"
+ J2_INITIAL_ANGLE, // j2 angle, radians, relative to "down"
+ 0, // j1 velocity, radians/sec
+ 0 // j2 velocity, radians/sec
+ );
+
+ // Derivation of the equations of motion via Lagrangian mechanics.
+ // This system is often referred to as a "double pendulum", and is a commonly studied problem,
+ // so literature is available. See this series of videos for an explanation:
+ // https://youtu.be/tc2ah-KnDXw, https://youtu.be/eBg8gof1RBg, https://youtu.be/QE1_H2vtHLU
+ // The formulation given below is slightly more generalized to allow arbitrary mass
+ // distributions along the arm links.
+ //
+ // Kinematics:
+ // x1 = J1_FIRST_MOMENT_OF_MASS / J1_MASS * sin(theta1)
+ // y1 = -J1_FIRST_MOMENT_OF_MASS / J1_MASS * cos(theta1)
+ // x2 = J1_LENGTH * sin(theta1) + J2_FIRST_MOMENT_OF_MASS / J2_MASS * sin(theta2)
+ // y2 = -J1_LENGTH * cos(theta1) - J2_FIRST_MOMENT_OF_MASS / J2_MASS * cos(theta2)
+ //
+ // Lagrangian:
+ // L = T - V
+ // = 1/2 * J1_MASS * (x1'^2 + y1'^2)
+ // + 1/2 * (J1_SECOND_MOMENT_OF_MASS - J1_FIRST_MOMENT_OF_MASS^2 / J1_MASS) * theta1'^2
+ // + 1/2 * J2_MASS * (x2'^2 + y2'^2)
+ // + 1/2 * (J2_SECOND_MOMENT_OF_MASS - J2_FIRST_MOMENT_OF_MASS^2 / J2_MASS) * theta2'^2
+ // - J1_MASS * y1 * PhysicalConstants.GRAVITY_ACCELERATION
+ // - J2_MASS * y2 * PhysicalConstants.GRAVITY_ACCELERATION
+ // = 1/2 * J1_SECOND_MOMENT_OF_MASS * theta1'^2
+ // + 1/2 * J2_MASS * ((J1_LENGTH * cos(theta1) * theta1' + J2_FIRST_MOMENT_OF_MASS / J2_MASS
+ // * cos(theta2) * theta2')^2 + (J1_LENGTH * sin(theta1) * theta1' + J2_FIRST_MOMENT_OF_MASS /
+ // J2_MASS * sin(theta2) * theta2')^2)
+ // + 1/2 * (J2_SECOND_MOMENT_OF_MASS - J2_FIRST_MOMENT_OF_MASS^2 / J2_MASS) * theta2'^2
+ // + J1_FIRST_MOMENT_OF_MASS * cos(theta1) * PhysicalConstants.GRAVITY_ACCELERATION
+ // + (J2_MASS * J1_LENGTH * cos(theta1) + J2_FIRST_MOMENT_OF_MASS * cos(theta2)) *
+ // PhysicalConstants.GRAVITY_ACCELERATION
+ // = 1/2 * J1_SECOND_MOMENT_OF_MASS * theta1'^2
+ // + 1/2 * (J2_MASS * J1_LENGTH^2 * theta1'^2 + 2 * J1_LENGTH * cos(theta1 - theta2) *
+ // theta1' * J2_FIRST_MOMENT_OF_MASS * theta2' + J2_FIRST_MOMENT_OF_MASS^2 / J2_MASS *
+ // theta2'^2)
+ // + 1/2 * (J2_SECOND_MOMENT_OF_MASS - J2_FIRST_MOMENT_OF_MASS^2 / J2_MASS) * theta2'^2
+ // + J1_FIRST_MOMENT_OF_MASS * cos(theta1) * PhysicalConstants.GRAVITY_ACCELERATION
+ // + (J2_MASS * J1_LENGTH * cos(theta1) + J2_FIRST_MOMENT_OF_MASS * cos(theta2)) *
+ // PhysicalConstants.GRAVITY_ACCELERATION
+ // = 1/2 * (J1_SECOND_MOMENT_OF_MASS + J2_MASS * J1_LENGTH^2) * theta1'^2
+ // + 1/2 * J2_SECOND_MOMENT_OF_MASS * theta2'^2
+ // + J2_FIRST_MOMENT_OF_MASS * J1_LENGTH * cos(theta1 - theta2) * theta1' * theta2'
+ // + (J1_FIRST_MOMENT_OF_MASS + J2_MASS * J1_LENGTH) *
+ // PhysicalConstants.GRAVITY_ACCELERATION * cos(theta1)
+ // + J2_FIRST_MOMENT_OF_MASS * PhysicalConstants.GRAVITY_ACCELERATION * cos(theta2)
+ //
+ // Define some constants to simplify the following equations
+ // I1 = J1_SECOND_MOMENT_OF_MASS + J2_MASS * J1_LENGTH^2
+ // I2 = J2_SECOND_MOMENT_OF_MASS
+ // Ix = J2_FIRST_MOMENT_OF_MASS * J1_LENGTH
+ // g1 = (J1_FIRST_MOMENT_OF_MASS + J2_MASS * J1_LENGTH) * PhysicalConstants.GRAVITY_ACCELERATION
+ // g2 = J2_FIRST_MOMENT_OF_MASS * PhysicalConstants.GRAVITY_ACCELERATION
+ //
+ // L = 1/2 * I1 * theta1'^2 + 1/2 * I2 * theta2'^2 + Ix * cos(theta1 - theta2) * theta1' *
+ // theta2' + g1 * cos(theta1) + g2 * cos(theta2)
+ //
+ // Euler-Lagrangian:
+ // d/dt [Partial]L/[Partial]theta' - [Partial]L/[Partial]theta = tau
+ //
+ // d/dt [Partial]L/[Partial]theta1' - [Partial]L/[Partial]theta1 = I1 * theta1'' + Ix *
+ // cos(theta1 - theta2) * theta2'' - Ix * sin(theta1 - theta2) * (theta1' - theta2') * theta2'
+ // + Ix * sin(theta1 - theta2) * theta1' * theta2' + g1 *
+ // sin(theta1)
+ // = I1 * theta1'' + Ix * cos(theta1 - theta2) * theta2'' + Ix *
+ // sin(theta1 - theta2) * theta2'^2 + g1 * sin(theta1)
+ // = tau1
+ //
+ // d/dt [Partial]L/[Partial]theta2' - [Partial]L/[Partial]theta2 = I2 * theta2'' + Ix *
+ // cos(theta1 - theta2) * theta1'' - Ix * sin(theta1 - theta2) * (theta1' - theta2') * theta1'
+ // - Ix * sin(theta1 - theta2) * theta1' * theta2' + g2 *
+ // sin(theta2))
+ // = I2 * theta2'' + Ix * cos(theta1 - theta2) * theta1'' - Ix *
+ // sin(theta1 - theta2) * theta1'^2 + g2 * sin(theta2)
+ // = tau2
+ //
+ // Rewrite as a matrix equation:
+ // M * theta'' + G = tau
+ // theta'' = M^-1 * (tau - G)
+ //
+ // M = [ I1, Ix * cos(theta1 - theta2) ]
+ // = [ Ix * cos(theta1 - theta2), I2 ]
+ // G = [ Ix * sin(theta1 - theta2) * theta1' * theta2' + g1 * sin(theta1) ]
+ // [ -Ix * sin(theta1 - theta2) * theta1' * theta2' + g2 * sin(theta2) ]
+
+ public DoubleJointedArm(ElectricalSystem electricalSystem) {
+ electricalSystem.addDevice(j1Controller);
+ electricalSystem.addDevice(j2Controller);
+ }
+
+ /**
+ * Advance the simulation model by the given amount of time.
+ */
+ public void step(double dt) {
+ final MechanicalAngularDevice.Output j1Drive =
+ j1Gears.step(new MechanicalAngularDevice.Input(getJ1Velocity()), dt);
+ final MechanicalAngularDevice.Output j2Drive =
+ j2Gears.step(new MechanicalAngularDevice.Input(getJ2Velocity()), dt);
+
+ double j1Limit = 0.;
+ if (state.get(0, 0) <= J1_MIN) {
+ state.set(0, 0, J1_MIN);
+ state.set(2, 0, 0.0);
+ j1Limit = 1.;
+ }
+ if (state.get(0, 0) >= J1_MAX) {
+ state.set(0, 0, J1_MAX);
+ state.set(2, 0, 0.0);
+ j1Limit = -1.;
+ }
+ double j2Limit = 0.;
+ if (state.get(1, 0) - state.get(0, 0) <= J2_MIN) {
+ state.set(1, 0, J2_MIN + state.get(0, 0));
+ j2Limit = 1.;
+ }
+ if (state.get(1, 0) - state.get(0, 0) >= J2_MAX) {
+ state.set(1, 0, J2_MAX + state.get(0, 0));
+ j2Limit = -1.;
+ }
+
+ if (state.get(2, 0) * j1Limit < 0) {
+ state.set(2, 0, 0.0);
+ }
+ if (state.get(3, 0) * j2Limit < 0) {
+ state.set(3, 0, 0.0);
+ }
+
+ final var u = VecBuilder.fill(j1Drive.torque, j2Drive.torque, j1Limit, j2Limit);
+ final var stateDot = S(state, u);
+ state = state.plus(stateDot.times(dt));
+
+ j1Controller.setSensorPosition(state.get(0, 0));
+ j2Controller.setSensorPosition(state.get(1, 0));
+ j1Controller.setSensorVelocity(state.get(2, 0));
+ j2Controller.setSensorVelocity(state.get(3, 0));
+ }
+
+ /**
+ * State space model X'(t) = S(X(t), u(t)) of the system
+ */
+ // Defined as a static method to make sure we don't accidentally refer to any of the state
+ // stored in class member variables when we should be referring to the state the integrator
+ // gave us.
+ private static Vector S(Matrix state, Matrix u) {
+ Vector motorTorques = VecBuilder.fill(u.get(0, 0), u.get(1, 0));
+ Vector jointLimits = VecBuilder.fill(u.get(2, 0), u.get(3, 0));
+ Vector velocity = VecBuilder.fill(state.get(2, 0), state.get(3, 0));
+
+ final double Mx =
+ J2_FIRST_MOMENT_OF_MASS * J1_LENGTH * Math.cos(state.get(0, 0) - state.get(1, 0));
+ var M =
+ Matrix.mat(N2.instance, N2.instance)
+ .fill(
+ J1_SECOND_MOMENT_OF_MASS + J2_MASS * J1_LENGTH * J1_LENGTH,
+ Mx,
+ Mx,
+ J2_SECOND_MOMENT_OF_MASS);
+
+ final double C =
+ J2_FIRST_MOMENT_OF_MASS
+ * J1_LENGTH
+ * Math.sin(state.get(0, 0) - state.get(1, 0))
+ * state.get(2, 0)
+ * state.get(3, 0);
+ final var G =
+ VecBuilder.fill(
+ C
+ + (J1_FIRST_MOMENT_OF_MASS + J2_MASS * J1_LENGTH)
+ * PhysicalConstants.GRAVITY_ACCELERATION
+ * Math.sin(state.get(0, 0)),
+ -C
+ + J2_FIRST_MOMENT_OF_MASS
+ * PhysicalConstants.GRAVITY_ACCELERATION
+ * Math.sin(state.get(1, 0)));
+ var T = motorTorques.minus(G);
+
+ if (T.get(0, 0) * jointLimits.get(0, 0) < 0) {
+ M.set(0, 0, 1.0);
+ M.set(1, 0, 0.0);
+ M.set(0, 1, 0.0);
+ T.set(0, 0, 0.0);
+ }
+ if (T.get(1, 0) * jointLimits.get(1, 0) < 0) {
+ M.set(1, 1, 1.0);
+ M.set(1, 0, 0.0);
+ M.set(0, 1, 0.0);
+ T.set(1, 0, 0.0);
+ }
+
+ final var acceleration = M.inv().times(T);
+
+ return VecBuilder.fill(
+ velocity.get(0, 0),
+ velocity.get(1, 0),
+ acceleration.get(0, 0),
+ acceleration.get(1, 0));
+ }
+
+ /**
+ * Return the angle of the first joint.
+ */
+ public double getJ1Angle() {
+ return state.get(0, 0);
+ }
+
+ /**
+ * Return the angle of the second joint.
+ */
+ public double getJ2Angle() {
+ return state.get(1, 0);
+ }
+
+ /**
+ * Return the velocity of the first joint.
+ */
+ public double getJ1Velocity() {
+ return state.get(2, 0);
+ }
+
+ /**
+ * Return the velocity of the second joint.
+ */
+ public double getJ2Velocity() {
+ return state.get(3, 0);
+ }
+
+ /**
+ * Calculate the total kinetic and potential energy in the arm.
+ *
+ * If no energy is added or removed (e.g. by motors or friction), this quantity should remain
+ * constant (i.e. conservation of energy), so plotting it over time makes a good sanity check of
+ * the model.
+ */
+ public double getEnergy() {
+ double y1 = -J1_FIRST_MOMENT_OF_MASS / J1_MASS * Math.cos(getJ1Angle());
+ double y2 =
+ -J1_LENGTH * Math.cos(getJ1Angle())
+ - J2_FIRST_MOMENT_OF_MASS / J2_MASS * Math.cos(getJ2Angle());
+ double dx1 = J1_FIRST_MOMENT_OF_MASS / J1_MASS * Math.cos(getJ1Angle()) * getJ1Velocity();
+ double dy1 = J1_FIRST_MOMENT_OF_MASS / J1_MASS * Math.sin(getJ1Angle()) * getJ1Velocity();
+ double dx2 =
+ J1_LENGTH * Math.cos(getJ1Angle()) * getJ1Velocity()
+ + J2_FIRST_MOMENT_OF_MASS
+ / J2_MASS
+ * Math.cos(getJ2Angle())
+ * getJ2Velocity();
+ double dy2 =
+ J1_LENGTH * Math.sin(getJ1Angle()) * getJ1Velocity()
+ + J2_FIRST_MOMENT_OF_MASS
+ / J2_MASS
+ * Math.sin(getJ2Angle())
+ * getJ2Velocity();
+
+ double energy =
+ 0.5 * J1_MASS * (dx1 * dx1 + dy1 * dy1)
+ + 0.5
+ * (J1_SECOND_MOMENT_OF_MASS
+ - J1_FIRST_MOMENT_OF_MASS
+ * J1_FIRST_MOMENT_OF_MASS
+ / J1_MASS)
+ * getJ1Velocity()
+ * getJ1Velocity()
+ + 0.5 * J2_MASS * (dx2 * dx2 + dy2 * dy2)
+ + 0.5
+ * (J2_SECOND_MOMENT_OF_MASS
+ - J2_FIRST_MOMENT_OF_MASS
+ * J2_FIRST_MOMENT_OF_MASS
+ / J2_MASS)
+ * getJ2Velocity()
+ * getJ2Velocity()
+ + J1_MASS * y1 * PhysicalConstants.GRAVITY_ACCELERATION
+ + J2_MASS * y2 * PhysicalConstants.GRAVITY_ACCELERATION;
+ return energy;
+ }
+
+ /**
+ * Calculuate the cartesian coordinates of the end of the first link.
+ */
+ public Vector getJ1Position() {
+ double x1 = J1_LENGTH * Math.sin(getJ1Angle());
+ double y1 = -J1_LENGTH * Math.cos(getJ1Angle());
+ return VecBuilder.fill(x1, y1);
+ }
+
+ /**
+ * Calculuate the cartesian coordinates of the end of the second link.
+ */
+ public Vector getJ2Position() {
+ double x2 = J1_LENGTH * Math.sin(getJ1Angle()) + J2_LENGTH * Math.sin(getJ2Angle());
+ double y2 = -J1_LENGTH * Math.cos(getJ1Angle()) - J2_LENGTH * Math.cos(getJ2Angle());
+ return VecBuilder.fill(x2, y2);
+ }
}
diff --git a/src/main/java/com/team766/simulator/mechanisms/WestCoastDrive.java b/src/main/java/com/team766/simulator/mechanisms/WestCoastDrive.java
index 8f96fbbb..458281b3 100644
--- a/src/main/java/com/team766/simulator/mechanisms/WestCoastDrive.java
+++ b/src/main/java/com/team766/simulator/mechanisms/WestCoastDrive.java
@@ -1,10 +1,5 @@
package com.team766.simulator.mechanisms;
-import org.apache.commons.math3.geometry.euclidean.threed.Rotation;
-import org.apache.commons.math3.geometry.euclidean.threed.RotationConvention;
-import org.apache.commons.math3.geometry.euclidean.threed.RotationOrder;
-import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
-
import com.team766.simulator.ElectricalSystem;
import com.team766.simulator.Parameters;
import com.team766.simulator.PhysicalConstants;
@@ -12,136 +7,166 @@
import com.team766.simulator.elements.DCMotor;
import com.team766.simulator.elements.DriveBase;
import com.team766.simulator.elements.Gears;
-import com.team766.simulator.elements.PwmMotorController;
import com.team766.simulator.elements.MotorController;
+import com.team766.simulator.elements.PwmMotorController;
import com.team766.simulator.elements.Wheel;
import com.team766.simulator.interfaces.MechanicalDevice;
+import org.apache.commons.math3.geometry.euclidean.threed.Rotation;
+import org.apache.commons.math3.geometry.euclidean.threed.RotationConvention;
+import org.apache.commons.math3.geometry.euclidean.threed.RotationOrder;
+import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
public class WestCoastDrive extends DriveBase {
- private DCMotor leftMotor = DCMotor.makeCIM("DriveLeftMotor");
- private DCMotor rightMotor = DCMotor.makeCIM("DriveRightMotor");
-
- private MotorController leftController = new PwmMotorController(6, leftMotor);
- private MotorController rightController = new PwmMotorController(4, rightMotor);
-
- private Gears leftGears = new Gears(Parameters.DRIVE_GEAR_RATIO, leftMotor);
- private Gears rightGears = new Gears(Parameters.DRIVE_GEAR_RATIO, rightMotor);
-
- private Wheel leftWheels = new Wheel(Parameters.DRIVE_WHEEL_DIAMETER, leftGears);
- private static final Vector3D LEFT_WHEEL_POSITION = new Vector3D(0., 0.3302, 0.);
- private Wheel rightWheels = new Wheel(Parameters.DRIVE_WHEEL_DIAMETER, rightGears);
- private static final Vector3D RIGHT_WHEEL_POSITION = new Vector3D(0., -0.3302, 0.);
- private static final double WHEEL_BASE = 0.585;
- private static final double ENCODER_TICKS_PER_METER = Parameters.ENCODER_TICKS_PER_REVOLUTION / (Parameters.DRIVE_WHEEL_DIAMETER * Math.PI);
-
- private Vector3D robotPosition = Vector3D.ZERO;
- private Rotation robotRotation = Rotation.IDENTITY;
- private Vector3D linearVelocity = Vector3D.ZERO;
- private Vector3D angularVelocity = Vector3D.ZERO;
- private Vector3D linearAcceleration = Vector3D.ZERO;
- private Vector3D angularAcceleration = Vector3D.ZERO;
-
- private double leftEncoderResidual = 0;
- private double rightEncoderResidual = 0;
-
- public WestCoastDrive(final ElectricalSystem electricalSystem) {
- electricalSystem.addDevice(leftController);
- electricalSystem.addDevice(rightController);
- }
-
- private static double softSignum(double x) {
- x /= 0.01;
- if (x > 1.0) {
- x = 1.0;
- } else if (x < -1.0) {
- x = -1.0;
- }
- return x;
- }
-
- public void step(double dt) {
- Vector3D wheelForce;
- Vector3D netForce = Vector3D.ZERO;
- Vector3D netTorque = Vector3D.ZERO;
- MechanicalDevice.Input leftWheelInput = new MechanicalDevice.Input(
- LEFT_WHEEL_POSITION,
- linearVelocity.scalarMultiply(-1.));
- wheelForce = leftWheels.step(leftWheelInput, dt).force.scalarMultiply(-1.0);
- netForce = netForce.add(wheelForce);
- netTorque = netTorque.add(Vector3D.crossProduct(LEFT_WHEEL_POSITION, wheelForce));
- MechanicalDevice.Input rightWheelInput = new MechanicalDevice.Input(
- RIGHT_WHEEL_POSITION,
- linearVelocity.scalarMultiply(-1.));
- wheelForce = rightWheels.step(rightWheelInput, dt).force.scalarMultiply(-1.0);
- netForce = netForce.add(wheelForce);
- netTorque = netTorque.add(Vector3D.crossProduct(RIGHT_WHEEL_POSITION, wheelForce));
-
- Vector3D ego_velocity = robotRotation.applyInverseTo(linearVelocity);
-
- double rateLeft = ENCODER_TICKS_PER_METER * (ego_velocity.getX() - angularVelocity.getZ() * LEFT_WHEEL_POSITION.getNorm());
- double rateRight = ENCODER_TICKS_PER_METER * (ego_velocity.getX() + angularVelocity.getZ() * RIGHT_WHEEL_POSITION.getNorm());
- leftEncoderResidual += rateLeft * dt;
- rightEncoderResidual += rateRight * dt;
- ProgramInterface.encoderChannels[0].distance += (long)leftEncoderResidual;
- ProgramInterface.encoderChannels[0].rate = rateLeft;
- ProgramInterface.encoderChannels[2].distance += (long) rightEncoderResidual;
- ProgramInterface.encoderChannels[2].rate = rateRight;
- leftEncoderResidual %= 1;
- rightEncoderResidual %= 1;
-
- ProgramInterface.gyro.angle = Math.toDegrees(robotRotation.getAngles(RotationOrder.XYZ, RotationConvention.VECTOR_OPERATOR)[2]);
- ProgramInterface.gyro.rate = Math.toDegrees(angularVelocity.getZ());
-
- Vector3D rollingResistance = new Vector3D(-softSignum(ego_velocity.getX()), 0.0, 0.0).scalarMultiply(
- Parameters.ROBOT_MASS * PhysicalConstants.GRAVITY_ACCELERATION * Parameters.ROLLING_RESISTANCE);
- netForce = netForce.add(rollingResistance);
-
- Vector3D transverseFriction = new Vector3D(0., -softSignum(ego_velocity.getY()), 0.).scalarMultiply(
- Parameters.WHEEL_COEFFICIENT_OF_FRICTION * Parameters.ROBOT_MASS * PhysicalConstants.GRAVITY_ACCELERATION);
- netForce = netForce.add(transverseFriction);
-
- double maxFriction = Parameters.WHEEL_COEFFICIENT_OF_FRICTION * Parameters.ROBOT_MASS * PhysicalConstants.GRAVITY_ACCELERATION * Parameters.TURNING_RESISTANCE_FACTOR;
- netTorque = netTorque.add(
- new Vector3D(0, 0, -softSignum(angularVelocity.getZ())).scalarMultiply(
- maxFriction * WHEEL_BASE / 2));
-
- linearAcceleration = robotRotation.applyTo(netForce).scalarMultiply(1.0 / Parameters.ROBOT_MASS);
- linearVelocity = linearVelocity.add(linearAcceleration.scalarMultiply(dt));
- robotPosition = robotPosition.add(linearVelocity.scalarMultiply(dt));
-
- angularAcceleration = netTorque.scalarMultiply(1.0 / Parameters.ROBOT_MOMENT_OF_INERTIA);
- angularVelocity = angularVelocity.add(angularAcceleration.scalarMultiply(dt));
- Vector3D angularDelta = angularVelocity.scalarMultiply(dt);
- robotRotation = robotRotation.compose(
- new Rotation(RotationOrder.XYZ,
- RotationConvention.VECTOR_OPERATOR,
- angularDelta.getX(),
- angularDelta.getY(),
- angularDelta.getZ()),
- RotationConvention.VECTOR_OPERATOR);
- }
-
- public Vector3D getPosition() {
- return robotPosition;
- }
-
- public Rotation getRotation() {
- return robotRotation;
- }
-
- public Vector3D getLinearVelocity() {
- return linearVelocity;
- }
-
- public Vector3D getAngularVelocity() {
- return angularVelocity;
- }
-
- public Vector3D getLinearAcceleration() {
- return linearAcceleration;
- }
-
- public Vector3D getAngularAcceleration() {
- return angularAcceleration;
- }
+ private DCMotor leftMotor = DCMotor.makeCIM("DriveLeftMotor");
+ private DCMotor rightMotor = DCMotor.makeCIM("DriveRightMotor");
+
+ private MotorController leftController = new PwmMotorController(6, leftMotor);
+ private MotorController rightController = new PwmMotorController(4, rightMotor);
+
+ private Gears leftGears = new Gears(Parameters.DRIVE_GEAR_RATIO, leftMotor);
+ private Gears rightGears = new Gears(Parameters.DRIVE_GEAR_RATIO, rightMotor);
+
+ private Wheel leftWheels = new Wheel(Parameters.DRIVE_WHEEL_DIAMETER, leftGears);
+ private static final Vector3D LEFT_WHEEL_POSITION = new Vector3D(0., 0.3302, 0.);
+ private Wheel rightWheels = new Wheel(Parameters.DRIVE_WHEEL_DIAMETER, rightGears);
+ private static final Vector3D RIGHT_WHEEL_POSITION = new Vector3D(0., -0.3302, 0.);
+ private static final double WHEEL_BASE = 0.585;
+ private static final double ENCODER_TICKS_PER_METER =
+ Parameters.ENCODER_TICKS_PER_REVOLUTION / (Parameters.DRIVE_WHEEL_DIAMETER * Math.PI);
+
+ private Vector3D robotPosition = Vector3D.ZERO;
+ private Rotation robotRotation = Rotation.IDENTITY;
+ private Vector3D linearVelocity = Vector3D.ZERO;
+ private Vector3D angularVelocity = Vector3D.ZERO;
+ private Vector3D linearAcceleration = Vector3D.ZERO;
+ private Vector3D angularAcceleration = Vector3D.ZERO;
+
+ private double leftEncoderResidual = 0;
+ private double rightEncoderResidual = 0;
+
+ public WestCoastDrive(final ElectricalSystem electricalSystem) {
+ electricalSystem.addDevice(leftController);
+ electricalSystem.addDevice(rightController);
+ }
+
+ private static double softSignum(double x) {
+ x /= 0.01;
+ if (x > 1.0) {
+ x = 1.0;
+ } else if (x < -1.0) {
+ x = -1.0;
+ }
+ return x;
+ }
+
+ public void step(double dt) {
+ Vector3D wheelForce;
+ Vector3D netForce = Vector3D.ZERO;
+ Vector3D netTorque = Vector3D.ZERO;
+ MechanicalDevice.Input leftWheelInput =
+ new MechanicalDevice.Input(LEFT_WHEEL_POSITION, linearVelocity.scalarMultiply(-1.));
+ wheelForce = leftWheels.step(leftWheelInput, dt).force.scalarMultiply(-1.0);
+ netForce = netForce.add(wheelForce);
+ netTorque = netTorque.add(Vector3D.crossProduct(LEFT_WHEEL_POSITION, wheelForce));
+ MechanicalDevice.Input rightWheelInput =
+ new MechanicalDevice.Input(
+ RIGHT_WHEEL_POSITION, linearVelocity.scalarMultiply(-1.));
+ wheelForce = rightWheels.step(rightWheelInput, dt).force.scalarMultiply(-1.0);
+ netForce = netForce.add(wheelForce);
+ netTorque = netTorque.add(Vector3D.crossProduct(RIGHT_WHEEL_POSITION, wheelForce));
+
+ Vector3D ego_velocity = robotRotation.applyInverseTo(linearVelocity);
+
+ double rateLeft =
+ ENCODER_TICKS_PER_METER
+ * (ego_velocity.getX()
+ - angularVelocity.getZ() * LEFT_WHEEL_POSITION.getNorm());
+ double rateRight =
+ ENCODER_TICKS_PER_METER
+ * (ego_velocity.getX()
+ + angularVelocity.getZ() * RIGHT_WHEEL_POSITION.getNorm());
+ leftEncoderResidual += rateLeft * dt;
+ rightEncoderResidual += rateRight * dt;
+ ProgramInterface.encoderChannels[0].distance += (long) leftEncoderResidual;
+ ProgramInterface.encoderChannels[0].rate = rateLeft;
+ ProgramInterface.encoderChannels[2].distance += (long) rightEncoderResidual;
+ ProgramInterface.encoderChannels[2].rate = rateRight;
+ leftEncoderResidual %= 1;
+ rightEncoderResidual %= 1;
+
+ ProgramInterface.gyro.angle =
+ Math.toDegrees(
+ robotRotation
+ .getAngles(RotationOrder.XYZ, RotationConvention.VECTOR_OPERATOR)[
+ 2]);
+ ProgramInterface.gyro.rate = Math.toDegrees(angularVelocity.getZ());
+
+ Vector3D rollingResistance =
+ new Vector3D(-softSignum(ego_velocity.getX()), 0.0, 0.0)
+ .scalarMultiply(
+ Parameters.ROBOT_MASS
+ * PhysicalConstants.GRAVITY_ACCELERATION
+ * Parameters.ROLLING_RESISTANCE);
+ netForce = netForce.add(rollingResistance);
+
+ Vector3D transverseFriction =
+ new Vector3D(0., -softSignum(ego_velocity.getY()), 0.)
+ .scalarMultiply(
+ Parameters.WHEEL_COEFFICIENT_OF_FRICTION
+ * Parameters.ROBOT_MASS
+ * PhysicalConstants.GRAVITY_ACCELERATION);
+ netForce = netForce.add(transverseFriction);
+
+ double maxFriction =
+ Parameters.WHEEL_COEFFICIENT_OF_FRICTION
+ * Parameters.ROBOT_MASS
+ * PhysicalConstants.GRAVITY_ACCELERATION
+ * Parameters.TURNING_RESISTANCE_FACTOR;
+ netTorque =
+ netTorque.add(
+ new Vector3D(0, 0, -softSignum(angularVelocity.getZ()))
+ .scalarMultiply(maxFriction * WHEEL_BASE / 2));
+
+ linearAcceleration =
+ robotRotation.applyTo(netForce).scalarMultiply(1.0 / Parameters.ROBOT_MASS);
+ linearVelocity = linearVelocity.add(linearAcceleration.scalarMultiply(dt));
+ robotPosition = robotPosition.add(linearVelocity.scalarMultiply(dt));
+
+ angularAcceleration = netTorque.scalarMultiply(1.0 / Parameters.ROBOT_MOMENT_OF_INERTIA);
+ angularVelocity = angularVelocity.add(angularAcceleration.scalarMultiply(dt));
+ Vector3D angularDelta = angularVelocity.scalarMultiply(dt);
+ robotRotation =
+ robotRotation.compose(
+ new Rotation(
+ RotationOrder.XYZ,
+ RotationConvention.VECTOR_OPERATOR,
+ angularDelta.getX(),
+ angularDelta.getY(),
+ angularDelta.getZ()),
+ RotationConvention.VECTOR_OPERATOR);
+ }
+
+ public Vector3D getPosition() {
+ return robotPosition;
+ }
+
+ public Rotation getRotation() {
+ return robotRotation;
+ }
+
+ public Vector3D getLinearVelocity() {
+ return linearVelocity;
+ }
+
+ public Vector3D getAngularVelocity() {
+ return angularVelocity;
+ }
+
+ public Vector3D getLinearAcceleration() {
+ return linearAcceleration;
+ }
+
+ public Vector3D getAngularAcceleration() {
+ return angularAcceleration;
+ }
}
diff --git a/src/main/java/com/team766/simulator/ui/ArmTrajectory.java b/src/main/java/com/team766/simulator/ui/ArmTrajectory.java
index 7ed2d893..3d7d52fc 100644
--- a/src/main/java/com/team766/simulator/ui/ArmTrajectory.java
+++ b/src/main/java/com/team766/simulator/ui/ArmTrajectory.java
@@ -1,103 +1,102 @@
package com.team766.simulator.ui;
+import de.erichseifert.gral.data.DataSeries;
+import de.erichseifert.gral.data.DataTable;
+import de.erichseifert.gral.plots.XYPlot;
+import de.erichseifert.gral.plots.lines.DefaultLineRenderer2D;
+import de.erichseifert.gral.plots.lines.LineRenderer;
+import de.erichseifert.gral.ui.InteractivePanel;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
-
import javax.swing.JPanel;
-import de.erichseifert.gral.data.DataSeries;
-import de.erichseifert.gral.data.DataTable;
-import de.erichseifert.gral.plots.XYPlot;
-import de.erichseifert.gral.plots.lines.DefaultLineRenderer2D;
-import de.erichseifert.gral.plots.lines.LineRenderer;
-import de.erichseifert.gral.ui.InteractivePanel;
-
/**
* UI Window that displays the movements that the robot's arm made.
*/
public class ArmTrajectory extends JPanel {
- private final XYPlot plot;
- private final DataTable data;
- // The current playback time.
- private double time;
+ private final XYPlot plot;
+ private final DataTable data;
+ // The current playback time.
+ private double time;
+
+ /**
+ * @param series The time series data defining the robot's trajectory.
+ * Each element should be an array with 5 values:
+ * [0]: Time
+ * [1]: X Position of the end of the first link
+ * [2]: Y Position of the end of the first link
+ * [3]: X Position of the end of the second link
+ * [4]: Y Position of the end of the second link
+ * @param playbackTimer The timer (shared between windows) which controls playback time.
+ */
+ public ArmTrajectory(Iterable series, PlaybackTimer playbackTimer) {
+ // Create an X-Y plot to display the trajectories
+ data = PlotUtils.makeDataTable(series);
+ var firstTrajectory = new DataSeries("First link", data, 1, 2);
+ var secondTrajectory = new DataSeries("Second link", data, 3, 4);
+ plot = new XYPlot(firstTrajectory, secondTrajectory);
+ plot.getAxis(XYPlot.AXIS_X).setRange(-2.5, 2.5);
+ plot.getAxis(XYPlot.AXIS_Y).setRange(-2.5, 2.5);
- /**
- * @param series The time series data defining the robot's trajectory.
- * Each element should be an array with 5 values:
- * [0]: Time
- * [1]: X Position of the end of the first link
- * [2]: Y Position of the end of the first link
- * [3]: X Position of the end of the second link
- * [4]: Y Position of the end of the second link
- * @param playbackTimer The timer (shared between windows) which controls playback time.
- */
- public ArmTrajectory(Iterable series, PlaybackTimer playbackTimer) {
- // Create an X-Y plot to display the trajectories
- data = PlotUtils.makeDataTable(series);
- var firstTrajectory = new DataSeries("First link", data, 1, 2);
- var secondTrajectory = new DataSeries("Second link", data, 3, 4);
- plot = new XYPlot(firstTrajectory, secondTrajectory);
- plot.getAxis(XYPlot.AXIS_X).setRange(-2.5, 2.5);
- plot.getAxis(XYPlot.AXIS_Y).setRange(-2.5, 2.5);
+ // Show the trajectories for the arm's links using different colors.
+ {
+ LineRenderer lines = new DefaultLineRenderer2D();
+ lines.setColor(new Color(0, 0, 128));
+ plot.setLineRenderers(firstTrajectory, lines);
+ plot.setPointRenderers(firstTrajectory, null);
+ }
+ {
+ LineRenderer lines = new DefaultLineRenderer2D();
+ lines.setColor(new Color(128, 0, 0));
+ plot.setLineRenderers(secondTrajectory, lines);
+ plot.setPointRenderers(secondTrajectory, null);
+ }
- // Show the trajectories for the arm's links using different colors.
- {
- LineRenderer lines = new DefaultLineRenderer2D();
- lines.setColor(new Color(0, 0, 128));
- plot.setLineRenderers(firstTrajectory, lines);
- plot.setPointRenderers(firstTrajectory, null);
- }
- {
- LineRenderer lines = new DefaultLineRenderer2D();
- lines.setColor(new Color(128, 0, 0));
- plot.setLineRenderers(secondTrajectory, lines);
- plot.setPointRenderers(secondTrajectory, null);
- }
-
- InteractivePanel panel = new InteractivePanel(plot);
- setLayout(new BorderLayout());
- add(panel, BorderLayout.CENTER);
+ InteractivePanel panel = new InteractivePanel(plot);
+ setLayout(new BorderLayout());
+ add(panel, BorderLayout.CENTER);
- // Add the standard time slider and play/pause button.
- add(new PlaybackControls(playbackTimer), BorderLayout.SOUTH);
+ // Add the standard time slider and play/pause button.
+ add(new PlaybackControls(playbackTimer), BorderLayout.SOUTH);
- // Add the callback that will update this window when playback time progresses.
- playbackTimer.addListener(event -> {
- this.time = (Double)event.getNewValue();
- this.repaint();
- });
- }
+ // Add the callback that will update this window when playback time progresses.
+ playbackTimer.addListener(
+ event -> {
+ this.time = (Double) event.getNewValue();
+ this.repaint();
+ });
+ }
- /**
- * Draw overlays on the plot
- */
- @Override
- public void paint(Graphics g) {
- super.paint(g);
+ /**
+ * Draw overlays on the plot
+ */
+ @Override
+ public void paint(Graphics g) {
+ super.paint(g);
- Graphics2D g2d = (Graphics2D)g;
+ Graphics2D g2d = (Graphics2D) g;
- int index = PlotUtils.findIndex(data.getColumn(0), time);
+ int index = PlotUtils.findIndex(data.getColumn(0), time);
- double j1_x = (Double)data.get(1, index);
- double j1_y = (Double)data.get(2, index);
- double j2_x = (Double)data.get(3, index);
- double j2_y = (Double)data.get(4, index);
+ double j1_x = (Double) data.get(1, index);
+ double j1_y = (Double) data.get(2, index);
+ double j2_x = (Double) data.get(3, index);
+ double j2_y = (Double) data.get(4, index);
- // Show the current position of the arm by drawing a line for each of the links.
+ // Show the current position of the arm by drawing a line for each of the links.
- Point j0_p = PlotUtils.getPixelCoords(plot, 0.0, 0.0);
- Point j1_p = PlotUtils.getPixelCoords(plot, j1_x, j1_y);
- Point j2_p = PlotUtils.getPixelCoords(plot, j2_x, j2_y);
+ Point j0_p = PlotUtils.getPixelCoords(plot, 0.0, 0.0);
+ Point j1_p = PlotUtils.getPixelCoords(plot, j1_x, j1_y);
+ Point j2_p = PlotUtils.getPixelCoords(plot, j2_x, j2_y);
- g2d.setStroke(new BasicStroke(10));
- g2d.setColor(new Color(128, 128, 255));
- g2d.drawLine(j0_p.x, j0_p.y, j1_p.x, j1_p.y);
- g2d.setColor(Color.red);
- g2d.drawLine(j1_p.x, j1_p.y, j2_p.x, j2_p.y);
- }
-}
\ No newline at end of file
+ g2d.setStroke(new BasicStroke(10));
+ g2d.setColor(new Color(128, 128, 255));
+ g2d.drawLine(j0_p.x, j0_p.y, j1_p.x, j1_p.y);
+ g2d.setColor(Color.red);
+ g2d.drawLine(j1_p.x, j1_p.y, j2_p.x, j2_p.y);
+ }
+}
diff --git a/src/main/java/com/team766/simulator/ui/MetricsPlot.java b/src/main/java/com/team766/simulator/ui/MetricsPlot.java
index 9e7e324b..b98b0ea0 100644
--- a/src/main/java/com/team766/simulator/ui/MetricsPlot.java
+++ b/src/main/java/com/team766/simulator/ui/MetricsPlot.java
@@ -1,5 +1,13 @@
package com.team766.simulator.ui;
+import com.team766.simulator.Metrics;
+import de.erichseifert.gral.data.DataSeries;
+import de.erichseifert.gral.data.DataSource;
+import de.erichseifert.gral.data.DataTable;
+import de.erichseifert.gral.plots.XYPlot;
+import de.erichseifert.gral.plots.lines.DefaultLineRenderer2D;
+import de.erichseifert.gral.plots.lines.LineRenderer;
+import de.erichseifert.gral.ui.InteractivePanel;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
@@ -12,171 +20,183 @@
import java.util.List;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
-import com.team766.simulator.Metrics;
-import de.erichseifert.gral.data.DataSeries;
-import de.erichseifert.gral.data.DataSource;
-import de.erichseifert.gral.data.DataTable;
-import de.erichseifert.gral.plots.XYPlot;
-import de.erichseifert.gral.plots.lines.DefaultLineRenderer2D;
-import de.erichseifert.gral.plots.lines.LineRenderer;
-import de.erichseifert.gral.ui.InteractivePanel;
/**
* UI Window that displays plots of time-series data.
*/
public class MetricsPlot extends JPanel {
- // Color palette from http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
- private static final String[] COLORS = {
- "#4D4D4D", // gray
- "#5DA5DA", // blue
- "#FAA43A", // orange
- "#60BD68", // green
- "#F17CB0", // pink
- "#B2912F", // brown
- "#B276B2", // purple
- "#DECF3F", // yellow
- "#F15854", // red
- };
-
- private static class DataSeriesWithMutableName extends DataSeries {
- public DataSeriesWithMutableName(String name, DataSource data, int... cols) {
- super(name, data, cols);
- }
-
- // Override to make this public
- @Override
- public void setName(String name) {
- super.setName(name);
- }
- }
-
- private final XYPlot plot;
- // The data store. Column 0 is time, the other columns are the metrics values.
- private final DataTable data;
- // A view into data for each of the series.
- private final DataSeriesWithMutableName[] sources;
- // A name for each of the series.
- private final List