-
Notifications
You must be signed in to change notification settings - Fork 84
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a widget for ProfiledPIDController (#764)
Effectively identical to PIDController for now, but future updates to WPILib will send more information
- Loading branch information
1 parent
bb5c1b6
commit 4edd2f5
Showing
5 changed files
with
266 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
120 changes: 120 additions & 0 deletions
120
.../src/main/java/edu/wpi/first/shuffleboard/plugin/base/data/ProfiledPIDControllerData.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package edu.wpi.first.shuffleboard.plugin.base.data; | ||
|
||
import com.google.common.collect.ImmutableMap; | ||
import edu.wpi.first.shuffleboard.api.data.ComplexData; | ||
|
||
import java.util.Map; | ||
import java.util.Objects; | ||
|
||
/** | ||
* Data for a profiled PID controller from WPILib. WPILib currently sends P, I, D, and the goal position; this makes | ||
* it effectively identical to {@link PIDControllerData}. Future updates to WPILib to fully support profiled PID | ||
* controllers will result in extra fields in the data; for example, the goal velocity or trapezoidal constraints. | ||
*/ | ||
public final class ProfiledPIDControllerData extends ComplexData<ProfiledPIDControllerData> { | ||
|
||
private final double p; | ||
private final double i; | ||
private final double d; | ||
private final double goal; | ||
|
||
/** | ||
* Creates a new PIDController data object. | ||
* | ||
* @param p the proportional constant | ||
* @param i the integral constant | ||
* @param d the derivative constant | ||
* @param goal the controller goal | ||
*/ | ||
public ProfiledPIDControllerData(double p, double i, double d, double goal) { | ||
this.p = p; | ||
this.i = i; | ||
this.d = d; | ||
this.goal = goal; | ||
} | ||
|
||
/** | ||
* Creates a new data object from a map. The map should contain values for all the properties of the data object. If | ||
* a value is missing, the default value of {@code 0} (for numbers) is used. | ||
*/ | ||
public ProfiledPIDControllerData(Map<String, Object> map) { | ||
this((double) map.getOrDefault("p", 0.0), | ||
(double) map.getOrDefault("i", 0.0), | ||
(double) map.getOrDefault("d", 0.0), | ||
(double) map.getOrDefault("goal", 0.0)); | ||
} | ||
|
||
public double getP() { | ||
return p; | ||
} | ||
|
||
public double getI() { | ||
return i; | ||
} | ||
|
||
public double getD() { | ||
return d; | ||
} | ||
|
||
public double getGoal() { | ||
return goal; | ||
} | ||
|
||
|
||
public ProfiledPIDControllerData withP(double p) { | ||
return new ProfiledPIDControllerData(p, i, d, goal); | ||
} | ||
|
||
public ProfiledPIDControllerData withI(double i) { | ||
return new ProfiledPIDControllerData(p, i, d, goal); | ||
} | ||
|
||
public ProfiledPIDControllerData withD(double d) { | ||
return new ProfiledPIDControllerData(p, i, d, goal); | ||
} | ||
|
||
public ProfiledPIDControllerData withGoal(double goal) { | ||
return new ProfiledPIDControllerData(p, i, d, goal); | ||
} | ||
|
||
@Override | ||
public Map<String, Object> asMap() { | ||
return ImmutableMap.<String, Object>builder() | ||
.put("p", p) | ||
.put("i", i) | ||
.put("d", d) | ||
.put("goal", goal) | ||
.build(); | ||
} | ||
|
||
@Override | ||
public boolean equals(Object obj) { | ||
if (this == obj) { | ||
return true; | ||
} | ||
if (obj == null || getClass() != obj.getClass()) { | ||
return false; | ||
} | ||
ProfiledPIDControllerData that = (ProfiledPIDControllerData) obj; | ||
return this.p == that.p | ||
&& this.i == that.i | ||
&& this.d == that.d | ||
&& this.goal == that.goal; | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(p, i, d, goal); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return String.format("ProfiledPIDControllerData(p=%s, i=%s, d=%s, goal=%s)", | ||
p, i, d, goal); | ||
} | ||
|
||
@Override | ||
public String toHumanReadableString() { | ||
return String.format("p=%.3f, i=%.3f, d=%.3f, goal=%.3f", p, i, d, goal); | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
...ain/java/edu/wpi/first/shuffleboard/plugin/base/data/types/ProfiledPIDControllerType.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package edu.wpi.first.shuffleboard.plugin.base.data.types; | ||
|
||
import edu.wpi.first.shuffleboard.api.data.ComplexDataType; | ||
import edu.wpi.first.shuffleboard.plugin.base.data.ProfiledPIDControllerData; | ||
|
||
import java.util.Map; | ||
import java.util.function.Function; | ||
|
||
public final class ProfiledPIDControllerType extends ComplexDataType<ProfiledPIDControllerData> { | ||
|
||
public static final ProfiledPIDControllerType Instance = new ProfiledPIDControllerType(); | ||
|
||
private ProfiledPIDControllerType() { | ||
super("ProfiledPIDController", ProfiledPIDControllerData.class); | ||
} | ||
|
||
@Override | ||
public Function<Map<String, Object>, ProfiledPIDControllerData> fromMap() { | ||
return ProfiledPIDControllerData::new; | ||
} | ||
|
||
@Override | ||
public ProfiledPIDControllerData getDefaultValue() { | ||
return new ProfiledPIDControllerData(0, 0, 0, 0); | ||
} | ||
|
||
} |
84 changes: 84 additions & 0 deletions
84
.../main/java/edu/wpi/first/shuffleboard/plugin/base/widget/ProfiledPIDControllerWidget.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package edu.wpi.first.shuffleboard.plugin.base.widget; | ||
|
||
import edu.wpi.first.shuffleboard.api.components.NumberField; | ||
import edu.wpi.first.shuffleboard.api.widget.Description; | ||
import edu.wpi.first.shuffleboard.api.widget.ParametrizedController; | ||
import edu.wpi.first.shuffleboard.api.widget.SimpleAnnotatedWidget; | ||
import edu.wpi.first.shuffleboard.plugin.base.data.ProfiledPIDControllerData; | ||
import javafx.event.ActionEvent; | ||
import javafx.fxml.FXML; | ||
import javafx.scene.control.TextField; | ||
import javafx.scene.layout.Pane; | ||
|
||
/** | ||
* A widget for controlling Profiled PID controllers. This gives control over the three PID constants and the controller | ||
* position goal. | ||
* | ||
* <p>Future updates to the sendable implementation for profiled PID controllers (such as support for goal velocity and | ||
* trapezoidal constraints) will result in more available controls in this widget. | ||
*/ | ||
@Description(name = "Profiled PID Controller", dataTypes = ProfiledPIDControllerData.class) | ||
@ParametrizedController("ProfiledPIDControllerWidget.fxml") | ||
public class ProfiledPIDControllerWidget extends SimpleAnnotatedWidget<ProfiledPIDControllerData> { | ||
|
||
@FXML | ||
private Pane root; | ||
@FXML | ||
private NumberField pField; | ||
@FXML | ||
private NumberField iField; | ||
@FXML | ||
private NumberField dField; | ||
@FXML | ||
private NumberField goalField; | ||
|
||
@FXML | ||
private void initialize() { | ||
root.setStyle("-fx-font-size: 10pt;"); | ||
dataProperty().addListener((__, old, newData) -> { | ||
pField.setNumber(newData.getP()); | ||
iField.setNumber(newData.getI()); | ||
dField.setNumber(newData.getD()); | ||
goalField.setNumber(newData.getGoal()); | ||
}); | ||
|
||
actOnFocusLost(pField); | ||
actOnFocusLost(iField); | ||
actOnFocusLost(dField); | ||
actOnFocusLost(goalField); | ||
} | ||
|
||
private void actOnFocusLost(TextField field) { | ||
field.focusedProperty().addListener((__, was, is) -> { | ||
if (!is) { | ||
field.getOnAction().handle(new ActionEvent(this, field)); | ||
} | ||
}); | ||
} | ||
|
||
@Override | ||
public Pane getView() { | ||
return root; | ||
} | ||
|
||
@FXML | ||
private void setP() { | ||
setData(getData().withP(pField.getNumber())); | ||
} | ||
|
||
@FXML | ||
private void setI() { | ||
setData(getData().withI(iField.getNumber())); | ||
} | ||
|
||
@FXML | ||
private void setD() { | ||
setData(getData().withD(dField.getNumber())); | ||
} | ||
|
||
@FXML | ||
private void setGoal() { | ||
setData(getData().withGoal(goalField.getNumber())); | ||
} | ||
|
||
} |
29 changes: 29 additions & 0 deletions
29
.../resources/edu/wpi/first/shuffleboard/plugin/base/widget/ProfiledPIDControllerWidget.fxml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
|
||
<?import edu.wpi.first.shuffleboard.api.components.NumberField?> | ||
<?import javafx.geometry.Insets?> | ||
<?import javafx.scene.control.Label?> | ||
<?import javafx.scene.layout.ColumnConstraints?> | ||
<?import javafx.scene.layout.GridPane?> | ||
<GridPane xmlns="http://javafx.com/javafx" | ||
xmlns:fx="http://javafx.com/fxml" | ||
fx:controller="edu.wpi.first.shuffleboard.plugin.base.widget.ProfiledPIDControllerWidget" | ||
fx:id="root" | ||
minWidth="112" minHeight="168" | ||
hgap="4" vgap="4"> | ||
<columnConstraints> | ||
<ColumnConstraints minWidth="52" prefWidth="52" maxWidth="52"/> | ||
<ColumnConstraints maxWidth="Infinity"/> | ||
</columnConstraints> | ||
<padding> | ||
<Insets topRightBottomLeft="8"/> | ||
</padding> | ||
<Label text="P" GridPane.columnIndex="0" GridPane.rowIndex="0"/> | ||
<Label text="I" GridPane.columnIndex="0" GridPane.rowIndex="1"/> | ||
<Label text="D" GridPane.columnIndex="0" GridPane.rowIndex="2"/> | ||
<Label text="Goal" GridPane.columnIndex="0" GridPane.rowIndex="3"/> | ||
<NumberField fx:id="pField" onAction="#setP" GridPane.columnIndex="1" GridPane.rowIndex="0" GridPane.hgrow="ALWAYS"/> | ||
<NumberField fx:id="iField" onAction="#setI" GridPane.columnIndex="1" GridPane.rowIndex="1" GridPane.hgrow="ALWAYS"/> | ||
<NumberField fx:id="dField" onAction="#setD" GridPane.columnIndex="1" GridPane.rowIndex="2" GridPane.hgrow="ALWAYS"/> | ||
<NumberField fx:id="goalField" onAction="#setGoal" GridPane.columnIndex="1" GridPane.rowIndex="3" GridPane.hgrow="ALWAYS"/> | ||
</GridPane> |