-
Notifications
You must be signed in to change notification settings - Fork 667
[cmd3] Add Commands v3-compatible SydIdRoutine #8304
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
DeltaDizzy
wants to merge
8
commits into
wpilibsuite:2027
Choose a base branch
from
DeltaDizzy:cmd3-sysid-api
base: 2027
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+303
−0
Open
Changes from 7 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
e5fe0bc
Add Commands v3-compatible SysIdRoutine
DeltaDizzy fc14827
run formatters
DeltaDizzy b71efd8
Refactor commands to eliminate unncessary children
DeltaDizzy abc4a27
eliminate constructor nulls
DeltaDizzy 13f31a3
use static factories
DeltaDizzy 2bb27db
run spotless
DeltaDizzy 2fb0039
add factory return javadocs
DeltaDizzy cf88079
mark inner class members package-private
DeltaDizzy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
303 changes: 303 additions & 0 deletions
303
commandsv3/src/main/java/org/wpilib/commands3/sysid/SysIdRoutine.java
This file contains hidden or 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,303 @@ | ||
| // Copyright (c) FIRST and other WPILib contributors. | ||
| // Open Source Software; you can modify and/or share it under the terms of | ||
| // the WPILib BSD license file in the root directory of this project. | ||
|
|
||
| package org.wpilib.commands3.sysid; | ||
|
|
||
| import static edu.wpi.first.units.Units.Second; | ||
| import static edu.wpi.first.units.Units.Seconds; | ||
| import static edu.wpi.first.units.Units.Volts; | ||
| import static edu.wpi.first.util.ErrorMessages.requireNonNullParam; | ||
|
|
||
| import edu.wpi.first.units.VoltageUnit; | ||
| import edu.wpi.first.units.measure.Time; | ||
| import edu.wpi.first.units.measure.Velocity; | ||
| import edu.wpi.first.units.measure.Voltage; | ||
| import edu.wpi.first.wpilibj.Timer; | ||
| import edu.wpi.first.wpilibj.sysid.SysIdRoutineLog; | ||
| import java.util.function.Consumer; | ||
| import org.wpilib.commands3.Command; | ||
| import org.wpilib.commands3.Mechanism; | ||
|
|
||
| /** | ||
| * A SysId characterization routine for a single mechanism. Mechanisms may have multiple motors. | ||
| * | ||
| * <p>A single subsystem may have multiple mechanisms, but mechanisms should not share test | ||
| * routines. Each complete test of a mechanism should have its own SysIdRoutine instance, since the | ||
| * log name of the recorded data is determined by the mechanism name. | ||
| * | ||
| * <p>The test state (e.g. "quasistatic-forward") is logged once per iteration during test | ||
| * execution, and once with state "none" when a test ends. Motor frames are logged every iteration | ||
| * during test execution. | ||
| * | ||
| * <p>Timestamps are not coordinated across data, so motor frames and test state tags may be | ||
| * recorded on different log frames. Because frame alignment is not guaranteed, SysId parses the log | ||
| * by using the test state flag to determine the timestamp range for each section of the test, and | ||
| * then extracts the motor frames within the valid timestamp ranges. If a given test was run | ||
| * multiple times in a single logfile, the user will need to select which of the tests to use for | ||
| * the fit in the analysis tool. | ||
| */ | ||
| public class SysIdRoutine extends SysIdRoutineLog { | ||
| private final Config m_config; | ||
| private final SysIdMechanism m_mechanism; | ||
| private final Consumer<State> m_recordState; | ||
|
|
||
| /** | ||
| * Create a new SysId characterization routine. | ||
| * | ||
| * @param config Hardware-independent parameters for the SysId routine. | ||
| * @param mechanism Hardware interface for the SysId routine. | ||
| */ | ||
| public SysIdRoutine(Config config, SysIdMechanism mechanism) { | ||
| super(mechanism.m_name); | ||
| m_config = config; | ||
| m_mechanism = mechanism; | ||
| m_recordState = config.m_recordState != null ? config.m_recordState : this::recordState; | ||
| } | ||
|
|
||
| /** Hardware-independent configuration for a SysId test routine. */ | ||
| public static class Config { | ||
| /** The voltage ramp rate used for quasistatic test routines. */ | ||
| public final Velocity<VoltageUnit> m_rampRate; | ||
|
|
||
| /** The step voltage output used for dynamic test routines. */ | ||
| public final Voltage m_stepVoltage; | ||
|
|
||
| /** Safety timeout for the test routine commands. */ | ||
| public final Time m_timeout; | ||
|
|
||
| /** Optional handle for recording test state in a third-party logging solution. */ | ||
| public final Consumer<State> m_recordState; | ||
|
|
||
| /** Default voltage ramp rate used in quasistatic test routines, set to 1 volt per second. */ | ||
| public static final Velocity<VoltageUnit> DEFAULT_RAMP_RATE = Volts.of(1).per(Second); | ||
|
|
||
| /** Default step voltage used in dynamic test routines, set to 7 volts. */ | ||
| public static final Voltage DEFAULT_STEP_VOLTAGE = Volts.of(7); | ||
|
|
||
| /** Default timeout used to automatically end test routine commands, set to 10 seconds. */ | ||
| public static final Time DEFAULT_TIMEOUT = Seconds.of(10); | ||
|
|
||
| /** | ||
| * Create a new configuration for a SysId test routine. To use the default config values, pass | ||
| * in the provided static default fields. | ||
| * | ||
| * @param rampRate The voltage ramp rate used for quasistatic test routines. | ||
| * @param stepVoltage The step voltage output used for dynamic test routines. | ||
| * @param timeout Safety timeout for the test routine commands. | ||
| * @param recordState Optional handle for recording test state in a third-party logging | ||
| * solution. If provided, the test routine state will be passed to this callback instead of | ||
| * logged in WPILog. | ||
| */ | ||
| public Config( | ||
| Velocity<VoltageUnit> rampRate, | ||
| Voltage stepVoltage, | ||
DeltaDizzy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Time timeout, | ||
| Consumer<State> recordState) { | ||
| requireNonNullParam(rampRate, "rampRate", "SysIdRoutine.Config"); | ||
| requireNonNullParam(stepVoltage, "stepVoltage", "SysIdRoutine.Config"); | ||
| requireNonNullParam(timeout, "timeout", "SysIdRoutine.Config"); | ||
| m_rampRate = rampRate; | ||
| m_stepVoltage = stepVoltage; | ||
| m_timeout = timeout; | ||
| m_recordState = recordState; | ||
| } | ||
|
|
||
| /** | ||
| * Create a new configuration for a SysId test routine. | ||
| * | ||
| * @param rampRate The voltage ramp rate used for quasistatic test routines. | ||
| * @param stepVoltage The step voltage output used for dynamic test routines. | ||
| * @param timeout Safety timeout for the test routine commands. | ||
| * @return A Config object with the specified ramp rate, step voltage, and timeout that records | ||
| * to a DataLog file. | ||
| */ | ||
| public static Config useDataLog( | ||
| Velocity<VoltageUnit> rampRate, Voltage stepVoltage, Time timeout) { | ||
| return new Config(rampRate, stepVoltage, timeout, null); | ||
| } | ||
|
|
||
| /** | ||
| * Create a default configuration for a SysId test routine with all default settings. | ||
| * | ||
| * <p>rampRate: 1 volt/sec | ||
| * | ||
| * <p>stepVoltage: 7 volts | ||
| * | ||
| * <p>timeout: 10 seconds | ||
| * | ||
| * @return A Config object with the default ramp rate, step voltage, and timeout that records to | ||
| * a DataLog file. | ||
| */ | ||
| public static Config defaults() { | ||
| return new Config(DEFAULT_RAMP_RATE, DEFAULT_STEP_VOLTAGE, DEFAULT_TIMEOUT, null); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * A mechanism to be characterized by a SysId routine. Defines callbacks needed for the SysId test | ||
| * routine to control and record data from the mechanism. | ||
| */ | ||
| public static class SysIdMechanism { | ||
| /** Sends the SysId-specified drive signal to the mechanism motors during test routines. */ | ||
| public final Consumer<? super Voltage> m_drive; | ||
|
|
||
| /** | ||
| * Returns measured data (voltages, positions, velocities) of the mechanism motors during test | ||
| * routines. | ||
| */ | ||
| public final Consumer<SysIdRoutineLog> m_log; | ||
|
|
||
| /** The subsystem containing the motor(s) that is (or are) being characterized. */ | ||
| private final Mechanism m_subsystem; | ||
|
|
||
| /** The name of the mechanism being tested. */ | ||
| public final String m_name; | ||
|
|
||
| /** | ||
| * Create a new mechanism specification for a SysId routine. | ||
| * | ||
| * @param drive Sends the SysId-specified drive signal to the mechanism motors during test | ||
| * routines. | ||
| * @param log Returns measured data of the mechanism motors during test routines. To return | ||
| * data, call `motor(string motorName)` on the supplied `SysIdRoutineLog` instance, and then | ||
| * call one or more of the chainable logging handles (e.g. `voltage`) on the returned | ||
| * `MotorLog`. Multiple motors can be logged in a single callback by calling `motor` | ||
| * multiple times. | ||
| * @param subsystem The subsystem containing the motor(s) that is (or are) being characterized. | ||
| * Will be declared as a requirement for the returned test commands. | ||
| * @param name The name of the mechanism being tested. Will be appended to the log entry title | ||
| * for the routine's test state, e.g. "sysid-test-state-mechanism". Defaults to the name of | ||
| * the subsystem if left null. | ||
| */ | ||
| public SysIdMechanism( | ||
| Consumer<Voltage> drive, Consumer<SysIdRoutineLog> log, Mechanism subsystem, String name) { | ||
| m_drive = drive; | ||
| m_log = log != null ? log : l -> {}; | ||
| m_subsystem = subsystem; | ||
| m_name = name != null ? name : subsystem.getName(); | ||
| } | ||
|
|
||
| /** | ||
| * Create a new mechanism specification for a SysId routine. Defaults the mechanism name to the | ||
| * subsystem name. | ||
| * | ||
| * @param drive Sends the SysId-specified drive signal to the mechanism motors during test | ||
| * routines. | ||
| * @param log Returns measured data of the mechanism motors during test routines. To return | ||
| * data, call `motor(string motorName)` on the supplied `SysIdRoutineLog` instance, and then | ||
| * call one or more of the chainable logging handles (e.g. `voltage`) on the returned | ||
| * `MotorLog`. Multiple motors can be logged in a single callback by calling `motor` | ||
| * multiple times. | ||
| * @param subsystem The subsystem containing the motor(s) that is (or are) being characterized. | ||
| * Will be declared as a requirement for the returned test commands. The subsystem's `name` | ||
| * will be appended to the log entry title for the routine's test state, e.g. | ||
| * "sysid-test-state-subsystem". | ||
| */ | ||
| public SysIdMechanism( | ||
| Consumer<Voltage> drive, Consumer<SysIdRoutineLog> log, Mechanism subsystem) { | ||
| this(drive, log, subsystem, null); | ||
| } | ||
| } | ||
|
|
||
| /** Motor direction for a SysId test. */ | ||
| public enum Direction { | ||
| /** Forward. */ | ||
| kForward, | ||
| /** Reverse. */ | ||
| kReverse | ||
| } | ||
|
|
||
| /** | ||
| * Returns a command to run a quasistatic test in the specified direction. | ||
| * | ||
| * <p>The command will call the `drive` and `log` callbacks supplied at routine construction once | ||
| * per iteration. Upon command end or interruption, the `drive` callback is called with a value of | ||
| * 0 volts. | ||
| * | ||
| * @param direction The direction in which to run the test. | ||
| * @return A command to run the test. | ||
| */ | ||
| public Command quasistatic(Direction direction) { | ||
| double outputSign = | ||
| switch (direction) { | ||
| case kForward -> 1.0; | ||
| case kReverse -> -1.0; | ||
| }; | ||
| State state = | ||
| switch (direction) { | ||
| case kForward -> State.kDynamicForward; | ||
| case kReverse -> State.kDynamicReverse; | ||
| }; | ||
|
|
||
| return m_mechanism | ||
| .m_subsystem | ||
| .run( | ||
| co -> { | ||
| Timer timer = new Timer(); | ||
| timer.start(); | ||
| while (!timer.hasElapsed(m_config.m_timeout.in(Seconds))) { | ||
| m_mechanism.m_drive.accept( | ||
| (Voltage) m_config.m_rampRate.times(Seconds.of(timer.get() * outputSign))); | ||
| m_mechanism.m_log.accept(this); | ||
| m_recordState.accept(state); | ||
| co.yield(); | ||
| } | ||
| m_mechanism.m_drive.accept(Volts.of(0)); | ||
| m_recordState.accept(State.kNone); | ||
DeltaDizzy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }) | ||
| .whenCanceled( | ||
| () -> { | ||
| m_mechanism.m_drive.accept(Volts.of(0)); | ||
| m_recordState.accept(State.kNone); | ||
| }) | ||
| .named("sysid-" + state.toString() + "-" + m_mechanism.m_name); | ||
| } | ||
|
|
||
| /** | ||
| * Returns a command to run a dynamic test in the specified direction. | ||
| * | ||
| * <p>The command will call the `drive` and `log` callbacks supplied at routine construction once | ||
| * per iteration. Upon command end or interruption, the `drive` callback is called with a value of | ||
| * 0 volts. | ||
| * | ||
| * @param direction The direction in which to run the test. | ||
| * @return A command to run the test. | ||
| */ | ||
| public Command dynamic(Direction direction) { | ||
| double outputSign = | ||
| switch (direction) { | ||
| case kForward -> 1.0; | ||
| case kReverse -> -1.0; | ||
| }; | ||
| State state = | ||
| switch (direction) { | ||
| case kForward -> State.kDynamicForward; | ||
| case kReverse -> State.kDynamicReverse; | ||
| }; | ||
|
|
||
| return m_mechanism | ||
| .m_subsystem | ||
| .run( | ||
| co -> { | ||
| Voltage output = m_config.m_stepVoltage.times(outputSign); | ||
| Timer timer = new Timer(); | ||
| timer.start(); | ||
|
|
||
| while (!timer.hasElapsed(m_config.m_timeout.in(Seconds))) { | ||
| m_mechanism.m_drive.accept(output); | ||
| m_mechanism.m_log.accept(this); | ||
| m_recordState.accept(state); | ||
| co.yield(); | ||
| } | ||
| m_mechanism.m_drive.accept(Volts.of(0)); | ||
| m_recordState.accept(State.kNone); | ||
| }) | ||
| .whenCanceled( | ||
| () -> { | ||
| m_mechanism.m_drive.accept(Volts.of(0)); | ||
| m_recordState.accept(State.kNone); | ||
| }) | ||
| .named("sysid-" + state + "-" + m_mechanism.m_name); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class should have tests. The v2 SysIdRoutine tests are a good starting point to copy over.