Add shooter superstructure code
Signed-off-by: Maxwell Henderson <mxwhenderson@gmail.com>
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
Change-Id: I559d79e1308c8ec6d434235e16f0273b4ce09f0d
diff --git a/y2024/BUILD b/y2024/BUILD
index 4aec7d2..0e8c31f 100644
--- a/y2024/BUILD
+++ b/y2024/BUILD
@@ -210,6 +210,7 @@
"//frc971/shooter_interpolation:interpolation",
"//frc971/zeroing:absolute_encoder",
"//frc971/zeroing:pot_and_absolute_encoder",
+ "//y2024/constants:constants_fbs",
"//y2024/control_loops/drivetrain:polydrivetrain_plants",
"//y2024/control_loops/superstructure/altitude:altitude_plants",
"//y2024/control_loops/superstructure/catapult:catapult_plants",
diff --git a/y2024/constants.h b/y2024/constants.h
index 3afc62c..0dce685 100644
--- a/y2024/constants.h
+++ b/y2024/constants.h
@@ -8,8 +8,10 @@
#include "frc971/constants.h"
#include "frc971/control_loops/pose.h"
#include "frc971/control_loops/static_zeroing_single_dof_profiled_subsystem.h"
+#include "frc971/shooter_interpolation/interpolation.h"
#include "frc971/zeroing/absolute_encoder.h"
#include "frc971/zeroing/pot_and_absolute_encoder.h"
+#include "y2024/constants/constants_generated.h"
#include "y2024/control_loops/drivetrain/drivetrain_dog_motor_plant.h"
#include "y2024/control_loops/superstructure/altitude/altitude_plant.h"
#include "y2024/control_loops/superstructure/catapult/catapult_plant.h"
@@ -174,6 +176,58 @@
// 20 -> 28 reduction to a 0.5" radius roller
static constexpr double kExtendRollerOutputRatio = (20.0 / 28.0) * 0.0127;
+ struct ShotParams {
+ // Measured in radians
+ double shot_altitude_angle = 0.0;
+ double shot_catapult_angle = 0.0;
+
+ // Muzzle velocity (m/s) of the game piece as it is released from the
+ // catapult.
+ double shot_velocity = 0.0;
+
+ // Speed over ground to use for shooting on the fly
+ double shot_speed_over_ground = 0.0;
+
+ static ShotParams BlendY(double coefficient, ShotParams a1, ShotParams a2) {
+ using ::frc971::shooter_interpolation::Blend;
+ return ShotParams{
+ .shot_altitude_angle = Blend(coefficient, a1.shot_altitude_angle,
+ a2.shot_altitude_angle),
+ .shot_catapult_angle = Blend(coefficient, a1.shot_catapult_angle,
+ a2.shot_catapult_angle),
+ .shot_velocity =
+ Blend(coefficient, a1.shot_velocity, a2.shot_velocity),
+ .shot_speed_over_ground =
+ Blend(coefficient, a1.shot_speed_over_ground,
+ a2.shot_speed_over_ground),
+ };
+ }
+
+ static ShotParams FromFlatbuffer(const y2024::ShotParams *shot_params) {
+ return ShotParams{
+ .shot_altitude_angle = shot_params->shot_altitude_angle(),
+ .shot_catapult_angle = shot_params->shot_catapult_angle(),
+ .shot_velocity = shot_params->shot_velocity(),
+ .shot_speed_over_ground = shot_params->shot_speed_over_ground()};
+ }
+ };
+
+ static frc971::shooter_interpolation::InterpolationTable<ShotParams>
+ InterpolationTableFromFlatbuffer(
+ const flatbuffers::Vector<
+ flatbuffers::Offset<y2024::InterpolationTablePoint>> *table) {
+ std::vector<std::pair<double, ShotParams>> interpolation_table;
+
+ for (const InterpolationTablePoint *point : *table) {
+ interpolation_table.emplace_back(
+ point->distance_from_goal(),
+ ShotParams::FromFlatbuffer(point->shot_params()));
+ }
+
+ return frc971::shooter_interpolation::InterpolationTable<ShotParams>(
+ interpolation_table);
+ }
+
struct PotAndAbsEncoderConstants {
::frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemParams<
::frc971::zeroing::PotAndAbsoluteEncoderZeroingEstimator>
diff --git a/y2024/constants/common.json b/y2024/constants/common.json
index e444f8d..cc7ace3 100644
--- a/y2024/constants/common.json
+++ b/y2024/constants/common.json
@@ -2,13 +2,22 @@
"target_map": {% include 'y2024/vision/maps/target_map.json' %},
"shooter_interpolation_table": [
{
- "distance_from_goal": 0.0,
+ "distance_from_goal": 5.0,
"shot_params": {
"shot_velocity": 0.0,
"shot_altitude_angle": 0.0,
"shot_catapult_angle": 0.0,
- "shot_speed_over_ground": 0.0
+ "shot_speed_over_ground": 2.0
}
+ },
+ {
+ "distance_from_goal": 10.0,
+ "shot_params": {
+ "shot_velocity": 0.0,
+ "shot_altitude_angle": 0.0,
+ "shot_catapult_angle": 0.0,
+ "shot_speed_over_ground": 4.0
+ }
}
],
"intake_roller_voltages": {
@@ -93,24 +102,22 @@
},
"loop": {% include 'y2024/control_loops/superstructure/climber/integral_climber_plant.json' %}
},
- "turret_loading_position": 0.0,
- "catapult_return_position": 0.0,
"catapult": {
"zeroing_voltage": 3.0,
"operating_voltage": 12.0,
"zeroing_profile_params": {
- "max_velocity": 0.5,
- "max_acceleration": 3.0
+ "max_velocity": 1.0,
+ "max_acceleration": 6.0
},
"default_profile_params":{
"max_velocity": 6.0,
"max_acceleration": 30.0
},
"range": {
- "lower_hard": -0.85,
- "upper_hard": 1.85,
- "lower": -0.400,
- "upper": 1.57
+ "lower_hard": -0.15971,
+ "upper_hard": 2.85,
+ "lower": -0.020,
+ "upper": 2.5
},
"loop": {% include 'y2024/control_loops/superstructure/catapult/integral_catapult_plant.json' %}
},
@@ -146,10 +153,10 @@
"max_acceleration": 30.0
},
"range": {
- "lower_hard": -0.85,
- "upper_hard": 1.85,
- "lower": -0.400,
- "upper": 1.57
+ "lower_hard": -2.36,
+ "upper_hard": 2.36,
+ "lower": -2.16,
+ "upper": 2.16
},
"loop": {% include 'y2024/control_loops/superstructure/turret/integral_turret_plant.json' %}
},
@@ -171,5 +178,37 @@
"upper": 1.57
},
"loop": {% include 'y2024/control_loops/superstructure/extend/integral_extend_plant.json' %}
+ },
+ "shooter_targets": {
+ "red_alliance": {
+ "pos": {
+ "rows": 3,
+ "cols": 1,
+ "storage_order": "ColMajor",
+ // The data field contains the x, y and z
+ // coordinates of the speaker on the red alliance
+ "data": [-8.0645, -1.4435, 2.0705]
+ },
+ "theta": 0.0
+ },
+ "blue_alliance": {
+ "pos": {
+ "rows": 3,
+ "cols": 1,
+ "storage_order": "ColMajor",
+ // The data field contains the x, y and z
+ // coordinates of the speaker on the blue alliance
+ "data": [8.0645, -1.4435, 2.0705]
+ },
+ "theta": 0.0
+ }
+ },
+ "altitude_loading_position": 0.0,
+ "turret_loading_position": 0.0,
+ "catapult_return_position": 0.0,
+ "min_altitude_shooting_angle": 0.3,
+ "retention_roller_voltages": {
+ "retaining": 1.5,
+ "spitting": -6.0
}
}
diff --git a/y2024/constants/constants.fbs b/y2024/constants/constants.fbs
index 633dc2e..f1c86a0 100644
--- a/y2024/constants/constants.fbs
+++ b/y2024/constants/constants.fbs
@@ -2,6 +2,7 @@
include "frc971/vision/target_map.fbs";
include "frc971/control_loops/profiled_subsystem.fbs";
include "frc971/zeroing/constants.fbs";
+include "frc971/math/matrix.fbs";
include "frc971/control_loops/drivetrain/drivetrain_config.fbs";
namespace y2024;
@@ -106,6 +107,23 @@
extend_constants:PotAndAbsEncoderConstants (id: 5);
}
+table Pose {
+ // Pos is a 3x1 matrix which contains the (x, y, z) component of the Pose.
+ pos: frc971.fbs.Matrix (id: 0);
+ theta: double (id: 1);
+}
+
+table ShooterTargets {
+ // The Pose of the red and blue alliance speakers we are aiming at.
+ red_alliance: Pose (id: 0);
+ blue_alliance: Pose (id: 1);
+}
+
+table RetentionRollerVoltages {
+ retaining:double (id: 0);
+ spitting:double (id: 1);
+}
+
// Common table for constants unrelated to the robot
table Common {
target_map:frc971.vision.TargetMap (id: 0);
@@ -113,7 +131,6 @@
intake_roller_voltages:IntakeRollerVoltages (id : 2);
intake_pivot_set_points:IntakePivotSetPoints (id: 3);
intake_pivot:frc971.control_loops.StaticZeroingSingleDOFProfiledSubsystemCommonParams (id: 4);
-
drivetrain:frc971.control_loops.drivetrain.fbs.DrivetrainConfig (id: 5);
current_limits:CurrentLimits (id: 6);
transfer_roller_voltages:TransferRollerVoltages (id: 7);
@@ -127,12 +144,17 @@
extend:frc971.control_loops.StaticZeroingSingleDOFProfiledSubsystemCommonParams (id: 15);
extend_setpoints:ExtendSetPoints (id: 16);
extend_roller_voltages:ExtendRollerVoltages (id: 17);
+ shooter_targets:ShooterTargets (id: 18);
+ altitude_loading_position: double (id: 19);
+ retention_roller_voltages:RetentionRollerVoltages (id: 20);
+
+ min_altitude_shooting_angle: double (id: 21);
}
table Constants {
- robot:RobotConstants (id: 0);
- common:Common (id: 1);
- cameras:[CameraConfiguration] (id: 2);
+ cameras:[CameraConfiguration] (id: 0);
+ robot:RobotConstants (id: 1);
+ common:Common (id: 2);
}
root_type Constants;
diff --git a/y2024/control_loops/superstructure/BUILD b/y2024/control_loops/superstructure/BUILD
index 28e4d00..0493c9b 100644
--- a/y2024/control_loops/superstructure/BUILD
+++ b/y2024/control_loops/superstructure/BUILD
@@ -81,6 +81,7 @@
data = [
],
deps = [
+ ":shooter",
":superstructure_goal_fbs",
":superstructure_output_fbs",
":superstructure_position_fbs",
@@ -140,6 +141,52 @@
],
)
+cc_library(
+ name = "shooter",
+ srcs = [
+ "shooter.cc",
+ ],
+ hdrs = [
+ "shooter.h",
+ ],
+ deps = [
+ ":aiming",
+ ":superstructure_can_position_fbs",
+ ":superstructure_goal_fbs",
+ ":superstructure_position_fbs",
+ ":superstructure_status_fbs",
+ "//frc971/control_loops:static_zeroing_single_dof_profiled_subsystem",
+ "//frc971/control_loops/catapult",
+ "//frc971/control_loops/catapult:catapult_goal_fbs",
+ "//frc971/shooter_interpolation:interpolation",
+ "//frc971/zeroing:pot_and_absolute_encoder",
+ "//y2024:constants",
+ "//y2024/constants:constants_fbs",
+ "//y2024/control_loops/superstructure/altitude:altitude_plants",
+ "//y2024/control_loops/superstructure/catapult:catapult_plants",
+ "//y2024/control_loops/superstructure/turret:turret_plants",
+ ],
+)
+
+cc_library(
+ name = "aiming",
+ srcs = [
+ "aiming.cc",
+ ],
+ hdrs = [
+ "aiming.h",
+ ],
+ deps = [
+ ":superstructure_status_fbs",
+ "//frc971/control_loops:static_zeroing_single_dof_profiled_subsystem",
+ "//frc971/control_loops/aiming",
+ "//frc971/control_loops/drivetrain:drivetrain_status_fbs",
+ "//y2024:constants",
+ "//y2024/constants:constants_fbs",
+ "//y2024/control_loops/drivetrain:drivetrain_base",
+ ],
+)
+
cc_binary(
name = "superstructure_replay",
srcs = ["superstructure_replay.cc"],
diff --git a/y2024/control_loops/superstructure/aiming.cc b/y2024/control_loops/superstructure/aiming.cc
new file mode 100644
index 0000000..980efba
--- /dev/null
+++ b/y2024/control_loops/superstructure/aiming.cc
@@ -0,0 +1,84 @@
+#include "y2024/control_loops/superstructure/aiming.h"
+
+#include "frc971/control_loops/aiming/aiming.h"
+#include "frc971/control_loops/pose.h"
+
+using frc971::control_loops::aiming::RobotState;
+using frc971::control_loops::aiming::ShotConfig;
+using frc971::control_loops::aiming::ShotMode;
+using y2024::control_loops::superstructure::Aimer;
+
+Aimer::Aimer(aos::EventLoop *event_loop,
+ const y2024::Constants *robot_constants)
+ : event_loop_(event_loop),
+ robot_constants_(CHECK_NOTNULL(robot_constants)),
+ drivetrain_config_(
+ frc971::control_loops::drivetrain::DrivetrainConfig<double>::
+ FromFlatbuffer(*robot_constants_->common()->drivetrain())),
+ interpolation_table_(
+ y2024::constants::Values::InterpolationTableFromFlatbuffer(
+ robot_constants_->common()->shooter_interpolation_table())),
+ joystick_state_fetcher_(
+ event_loop_->MakeFetcher<aos::JoystickState>("/aos")) {}
+
+void Aimer::Update(
+ const frc971::control_loops::drivetrain::Status *status,
+ frc971::control_loops::aiming::ShotMode shot_mode,
+ frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoalStatic
+ *turret_goal) {
+ const frc971::control_loops::Pose robot_pose({status->x(), status->y(), 0},
+ status->theta());
+ joystick_state_fetcher_.Fetch();
+ CHECK_NOTNULL(joystick_state_fetcher_.get());
+
+ aos::Alliance alliance = joystick_state_fetcher_->alliance();
+
+ const frc971::control_loops::Pose red_alliance_goal(
+ frc971::ToEigenOrDie<3, 1>(*robot_constants_->common()
+ ->shooter_targets()
+ ->red_alliance()
+ ->pos()),
+ robot_constants_->common()->shooter_targets()->red_alliance()->theta());
+
+ const frc971::control_loops::Pose blue_alliance_goal(
+ frc971::ToEigenOrDie<3, 1>(*robot_constants_->common()
+ ->shooter_targets()
+ ->blue_alliance()
+ ->pos()),
+ robot_constants_->common()->shooter_targets()->blue_alliance()->theta());
+
+ const frc971::control_loops::Pose goal =
+ alliance == aos::Alliance::kRed ? red_alliance_goal : blue_alliance_goal;
+
+ const Eigen::Vector2d linear_angular =
+ drivetrain_config_.Tlr_to_la() *
+ Eigen::Vector2d(status->estimated_left_velocity(),
+ status->estimated_right_velocity());
+ const double xdot = linear_angular(0) * std::cos(status->theta());
+ const double ydot = linear_angular(0) * std::sin(status->theta());
+
+ // Use the previous shot distance to estimate the speed-over-ground of the
+ // note.
+ current_goal_ = frc971::control_loops::aiming::AimerGoal(
+ ShotConfig{goal, shot_mode,
+ frc971::constants::Range::FromFlatbuffer(
+ robot_constants_->common()->turret()->range()),
+ interpolation_table_.Get(current_goal_.target_distance)
+ .shot_speed_over_ground,
+ /*wrap_mode=*/0.0, /*turret_zero_offset*/ 0.0},
+ RobotState{
+ robot_pose, {xdot, ydot}, linear_angular(1), current_goal_.position});
+
+ turret_goal->set_unsafe_goal(current_goal_.position);
+ turret_goal->set_goal_velocity(current_goal_.velocity);
+}
+
+flatbuffers::Offset<AimerStatus> Aimer::PopulateStatus(
+ flatbuffers::FlatBufferBuilder *fbb) const {
+ AimerStatus::Builder builder(*fbb);
+ builder.add_turret_position(current_goal_.position);
+ builder.add_turret_velocity(current_goal_.velocity);
+ builder.add_target_distance(current_goal_.target_distance);
+ builder.add_shot_distance(DistanceToGoal());
+ return builder.Finish();
+}
diff --git a/y2024/control_loops/superstructure/aiming.h b/y2024/control_loops/superstructure/aiming.h
new file mode 100644
index 0000000..21d427a
--- /dev/null
+++ b/y2024/control_loops/superstructure/aiming.h
@@ -0,0 +1,51 @@
+#ifndef Y2024_CONTROL_LOOPS_SUPERSTRUCTURE_AIMING_H_
+#define Y2024_CONTROL_LOOPS_SUPERSTRUCTURE_AIMING_H_
+
+#include "frc971/control_loops/aiming/aiming.h"
+#include "frc971/control_loops/drivetrain/drivetrain_status_generated.h"
+#include "frc971/control_loops/pose.h"
+#include "frc971/control_loops/static_zeroing_single_dof_profiled_subsystem.h"
+#include "frc971/shooter_interpolation/interpolation.h"
+#include "y2024/constants.h"
+#include "y2024/constants/constants_generated.h"
+#include "y2024/control_loops/drivetrain/drivetrain_base.h"
+#include "y2024/control_loops/superstructure/superstructure_status_generated.h"
+
+using y2024::control_loops::superstructure::AimerStatus;
+
+namespace y2024::control_loops::superstructure {
+
+class Aimer {
+ public:
+ Aimer(aos::EventLoop *event_loop, const Constants *robot_constants);
+
+ void Update(
+ const frc971::control_loops::drivetrain::Status *status,
+ frc971::control_loops::aiming::ShotMode shot_mode,
+ frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoalStatic
+ *turret_goal);
+
+ double DistanceToGoal() const { return current_goal_.virtual_shot_distance; }
+
+ flatbuffers::Offset<AimerStatus> PopulateStatus(
+ flatbuffers::FlatBufferBuilder *fbb) const;
+
+ private:
+ aos::EventLoop *event_loop_;
+
+ const Constants *robot_constants_;
+
+ frc971::control_loops::drivetrain::DrivetrainConfig<double>
+ drivetrain_config_;
+
+ frc971::shooter_interpolation::InterpolationTable<
+ y2024::constants::Values::ShotParams>
+ interpolation_table_;
+
+ aos::Fetcher<aos::JoystickState> joystick_state_fetcher_;
+
+ frc971::control_loops::aiming::TurretGoal current_goal_;
+};
+
+} // namespace y2024::control_loops::superstructure
+#endif // Y2024_CONTROL_LOOPS_SUPERSTRUCTURE_TURRET_AIMING_H_
diff --git a/y2024/control_loops/superstructure/shooter.cc b/y2024/control_loops/superstructure/shooter.cc
new file mode 100644
index 0000000..0666772
--- /dev/null
+++ b/y2024/control_loops/superstructure/shooter.cc
@@ -0,0 +1,271 @@
+#include "y2024/control_loops/superstructure/shooter.h"
+
+#include "aos/flatbuffers.h"
+#include "aos/flatbuffers/base.h"
+#include "frc971/control_loops/aiming/aiming.h"
+#include "y2024/control_loops/superstructure/catapult/catapult_plant.h"
+
+namespace y2024::control_loops::superstructure {
+
+using frc971::control_loops::PotAndAbsoluteEncoderProfiledJointStatus;
+
+constexpr double kMinCurrent = 20.0;
+constexpr double kMaxVelocity = 1.0;
+constexpr double kCatapultActivationThreshold = 0.01;
+
+Shooter::Shooter(aos::EventLoop *event_loop, const Constants *robot_constants)
+ : drivetrain_status_fetcher_(
+ event_loop->MakeFetcher<frc971::control_loops::drivetrain::Status>(
+ "/drivetrain")),
+ superstructure_can_position_fetcher_(
+ event_loop
+ ->MakeFetcher<y2024::control_loops::superstructure::CANPosition>(
+ "/superstructure/rio")),
+ robot_constants_(robot_constants),
+ catapult_(
+ robot_constants->common()->catapult(),
+ robot_constants->robot()->catapult_constants()->zeroing_constants()),
+ turret_(
+ robot_constants_->common()->turret(),
+ robot_constants_->robot()->turret_constants()->zeroing_constants()),
+ altitude_(
+ robot_constants_->common()->altitude(),
+ robot_constants_->robot()->altitude_constants()->zeroing_constants()),
+ aimer_(event_loop, robot_constants_),
+ interpolation_table_(
+ y2024::constants::Values::InterpolationTableFromFlatbuffer(
+ robot_constants_->common()->shooter_interpolation_table())) {}
+
+flatbuffers::Offset<y2024::control_loops::superstructure::ShooterStatus>
+Shooter::Iterate(
+ const y2024::control_loops::superstructure::Position *position,
+ const y2024::control_loops::superstructure::ShooterGoal *shooter_goal,
+ double *catapult_output, double *altitude_output, double *turret_output,
+ double *retention_roller_output, double /*battery_voltage*/,
+ aos::monotonic_clock::time_point current_timestamp,
+ flatbuffers::FlatBufferBuilder *fbb) {
+ superstructure_can_position_fetcher_.Fetch();
+ drivetrain_status_fetcher_.Fetch();
+ CHECK(superstructure_can_position_fetcher_.get() != nullptr);
+
+ double current_retention_position =
+ superstructure_can_position_fetcher_->retention_roller()->position();
+
+ double torque_current =
+ superstructure_can_position_fetcher_->retention_roller()
+ ->torque_current();
+
+ double retention_velocity =
+ (current_retention_position - last_retention_position_) /
+ std::chrono::duration<double>(current_timestamp - last_timestamp_)
+ .count();
+
+ // If our current is over the minimum current and our velocity is under our
+ // maximum velocity, then set loaded to true. If we are preloaded set it to
+ // true as well.
+ //
+ // TODO(austin): Debounce piece_loaded?
+ bool piece_loaded =
+ (torque_current > kMinCurrent && retention_velocity < kMaxVelocity) ||
+ (shooter_goal != nullptr && shooter_goal->preloaded());
+
+ aos::fbs::FixedStackAllocator<aos::fbs::Builder<
+ frc971::control_loops::
+ StaticZeroingSingleDOFProfiledSubsystemGoalStatic>::kBufferSize>
+ turret_allocator;
+
+ aos::fbs::Builder<
+ frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoalStatic>
+ turret_goal_builder(&turret_allocator);
+
+ aos::fbs::FixedStackAllocator<aos::fbs::Builder<
+ frc971::control_loops::
+ StaticZeroingSingleDOFProfiledSubsystemGoalStatic>::kBufferSize>
+ altitude_allocator;
+
+ aos::fbs::Builder<
+ frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoalStatic>
+ altitude_goal_builder(&altitude_allocator);
+
+ const double distance_to_goal = aimer_.DistanceToGoal();
+
+ // Always retain the game piece if we are enabled.
+ if (retention_roller_output != nullptr) {
+ *retention_roller_output =
+ robot_constants_->common()->retention_roller_voltages()->retaining();
+ }
+
+ bool aiming = false;
+
+ // We don't have the note so we should be ready to intake it.
+ if (shooter_goal == nullptr || !shooter_goal->auto_aim() ||
+ (!piece_loaded && state_ == CatapultState::READY)) {
+ PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
+ turret_goal_builder.get(),
+ robot_constants_->common()->turret_loading_position());
+
+ PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
+ altitude_goal_builder.get(),
+ robot_constants_->common()->altitude_loading_position());
+ } else {
+ // We have a game piece, lets start aiming.
+ if (drivetrain_status_fetcher_.get() != nullptr) {
+ aiming = true;
+ aimer_.Update(drivetrain_status_fetcher_.get(),
+ frc971::control_loops::aiming::ShotMode::kShootOnTheFly,
+ turret_goal_builder.get());
+ }
+ }
+
+ // We have a game piece and are being asked to aim.
+ constants::Values::ShotParams shot_params;
+ if (piece_loaded && shooter_goal != nullptr && shooter_goal->auto_aim() &&
+ interpolation_table_.GetInRange(distance_to_goal, &shot_params)) {
+ PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
+ altitude_goal_builder.get(), shot_params.shot_altitude_angle);
+ }
+
+ // The builder will contain either the auto-aim goal, or the loading goal. Use
+ // it if we have no goal, or no subsystem goal, or if we are auto-aiming.
+ const frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal
+ *turret_goal = (shooter_goal != nullptr && !shooter_goal->auto_aim() &&
+ shooter_goal->has_turret_position())
+ ? shooter_goal->turret_position()
+ : &turret_goal_builder->AsFlatbuffer();
+ const frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal
+ *altitude_goal = (shooter_goal != nullptr && !shooter_goal->auto_aim() &&
+ shooter_goal->has_altitude_position())
+ ? shooter_goal->altitude_position()
+ : &altitude_goal_builder->AsFlatbuffer();
+
+ bool subsystems_in_range =
+ (std::abs(turret_.estimated_position() - turret_goal->unsafe_goal()) <
+ kCatapultActivationThreshold &&
+ std::abs(altitude_.estimated_position() - altitude_goal->unsafe_goal()) <
+ kCatapultActivationThreshold &&
+ altitude_.estimated_position() >
+ robot_constants_->common()->min_altitude_shooting_angle());
+
+ const flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
+ turret_status_offset =
+ turret_.Iterate(turret_goal, position->turret(), turret_output, fbb);
+
+ const flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
+ altitude_status_offset = altitude_.Iterate(
+ altitude_goal, position->altitude(), altitude_output, fbb);
+
+ flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
+ catapult_status_offset;
+ {
+ // The catapult will never use a provided goal. We'll always fabricate one
+ // for it.
+ //
+ // Correct handles resetting our state when disabled.
+ const bool disabled = catapult_.Correct(nullptr, position->catapult(),
+ shooter_goal == nullptr);
+
+ catapult_.set_enable_profile(true);
+ // We want a trajectory which accelerates up over the first portion of the
+ // range of motion, holds top speed for a little bit, then decelerates
+ // before it swings too far.
+ //
+ // We can solve for these 3 parameters through the range of motion. Top
+ // speed is goverened by the voltage headroom we want to have for the
+ // controller.
+ //
+ // Accel can be tuned given the distance to accelerate over, and decel can
+ // be solved similarly.
+ //
+ // accel = v^2 / (2 * x)
+ catapult_.mutable_profile()->set_maximum_velocity(
+ catapult::kFreeSpeed * catapult::kOutputRatio * 4.0 / 12.0);
+
+ if (disabled) {
+ state_ = CatapultState::RETRACTING;
+ }
+
+ switch (state_) {
+ case CatapultState::READY:
+ case CatapultState::LOADED: {
+ if (piece_loaded) {
+ state_ = CatapultState::LOADED;
+ } else {
+ state_ = CatapultState::READY;
+ }
+
+ const bool catapult_close = CatapultClose();
+
+ if (subsystems_in_range && shooter_goal != nullptr &&
+ shooter_goal->fire() && catapult_close && piece_loaded) {
+ state_ = CatapultState::FIRING;
+ } else {
+ catapult_.set_controller_index(0);
+ catapult_.mutable_profile()->set_maximum_acceleration(100.0);
+ catapult_.mutable_profile()->set_maximum_deceleration(50.0);
+ catapult_.set_unprofiled_goal(0.0, 0.0);
+
+ if (!catapult_close) {
+ state_ = CatapultState::RETRACTING;
+ }
+ break;
+ }
+ [[fallthrough]];
+ }
+ case CatapultState::FIRING:
+ *retention_roller_output =
+ robot_constants_->common()->retention_roller_voltages()->spitting();
+ catapult_.set_controller_index(0);
+ catapult_.mutable_profile()->set_maximum_acceleration(400.0);
+ catapult_.mutable_profile()->set_maximum_deceleration(600.0);
+ catapult_.set_unprofiled_goal(2.0, 0.0);
+ if (CatapultClose()) {
+ state_ = CatapultState::RETRACTING;
+ } else {
+ break;
+ }
+ [[fallthrough]];
+ case CatapultState::RETRACTING:
+ catapult_.set_controller_index(0);
+ catapult_.mutable_profile()->set_maximum_acceleration(100.0);
+ catapult_.mutable_profile()->set_maximum_deceleration(50.0);
+ catapult_.set_unprofiled_goal(0.0, 0.0);
+
+ if (CatapultClose()) {
+ if (piece_loaded) {
+ state_ = CatapultState::LOADED;
+ } else {
+ state_ = CatapultState::READY;
+ }
+ }
+ break;
+ }
+
+ const double voltage = catapult_.UpdateController(disabled);
+ catapult_.UpdateObserver(voltage);
+ if (catapult_output != nullptr) {
+ *catapult_output = voltage;
+ }
+ catapult_status_offset = catapult_.MakeStatus(fbb);
+ }
+
+ flatbuffers::Offset<AimerStatus> aimer_offset;
+ if (aiming) {
+ aimer_offset = aimer_.PopulateStatus(fbb);
+ }
+
+ y2024::control_loops::superstructure::ShooterStatus::Builder status_builder(
+ *fbb);
+ status_builder.add_turret(turret_status_offset);
+ status_builder.add_altitude(altitude_status_offset);
+ status_builder.add_catapult(catapult_status_offset);
+ status_builder.add_catapult_state(state_);
+ if (aiming) {
+ status_builder.add_aimer(aimer_offset);
+ }
+
+ last_retention_position_ = current_retention_position;
+ last_timestamp_ = current_timestamp;
+ return status_builder.Finish();
+}
+
+} // namespace y2024::control_loops::superstructure
diff --git a/y2024/control_loops/superstructure/shooter.h b/y2024/control_loops/superstructure/shooter.h
new file mode 100644
index 0000000..b381f3e
--- /dev/null
+++ b/y2024/control_loops/superstructure/shooter.h
@@ -0,0 +1,108 @@
+#ifndef Y2024_CONTROL_LOOPS_SUPERSTRUCTURE_SHOOTER_H_
+#define Y2024_CONTROL_LOOPS_SUPERSTRUCTURE_SHOOTER_H_
+
+#include "frc971/control_loops/catapult/catapult.h"
+#include "frc971/control_loops/catapult/catapult_goal_static.h"
+#include "frc971/control_loops/static_zeroing_single_dof_profiled_subsystem.h"
+#include "frc971/shooter_interpolation/interpolation.h"
+#include "frc971/zeroing/pot_and_absolute_encoder.h"
+#include "y2024/constants.h"
+#include "y2024/constants/constants_generated.h"
+#include "y2024/control_loops/superstructure/aiming.h"
+#include "y2024/control_loops/superstructure/superstructure_can_position_generated.h"
+#include "y2024/control_loops/superstructure/superstructure_goal_generated.h"
+#include "y2024/control_loops/superstructure/superstructure_position_generated.h"
+#include "y2024/control_loops/superstructure/superstructure_status_generated.h"
+
+namespace y2024::control_loops::superstructure {
+
+// The shooter class will control the various subsystems involved in the
+// shooter- the turret, altitude, and catapult.
+class Shooter {
+ public:
+ using PotAndAbsoluteEncoderSubsystem =
+ ::frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystem<
+ ::frc971::zeroing::PotAndAbsoluteEncoderZeroingEstimator,
+ ::frc971::control_loops::PotAndAbsoluteEncoderProfiledJointStatus>;
+
+ using CatapultSubsystem =
+ ::frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystem<
+ ::frc971::zeroing::PotAndAbsoluteEncoderZeroingEstimator,
+ ::frc971::control_loops::PotAndAbsoluteEncoderProfiledJointStatus,
+ ::frc971::zeroing::PotAndAbsoluteEncoderZeroingEstimator,
+ aos::util::AsymmetricTrapezoidProfile>;
+
+ Shooter(aos::EventLoop *event_loop, const Constants *robot_constants);
+
+ void Reset() {
+ catapult_.Reset();
+ turret_.Reset();
+ altitude_.Reset();
+ }
+
+ void Estop() {
+ catapult_.Estop();
+ turret_.Estop();
+ altitude_.Estop();
+ }
+
+ bool zeroed() {
+ return catapult_.zeroed() && turret_.zeroed() && altitude_.zeroed();
+ }
+
+ bool estopped() {
+ return catapult_.estopped() && turret_.estopped() && altitude_.estopped();
+ }
+
+ inline const PotAndAbsoluteEncoderSubsystem &turret() const {
+ return turret_;
+ }
+
+ inline const PotAndAbsoluteEncoderSubsystem &altitude() const {
+ return altitude_;
+ }
+
+ flatbuffers::Offset<ShooterStatus> Iterate(
+ const Position *position, const ShooterGoal *shooter_goal,
+ double *catapult_output, double *altitude_output, double *turret_output,
+ double *retention_roller_output, double battery_voltage,
+ aos::monotonic_clock::time_point current_timestamp,
+ flatbuffers::FlatBufferBuilder *fbb);
+
+ private:
+ CatapultState state_ = CatapultState::RETRACTING;
+
+ bool CatapultClose() const {
+ return (std::abs(catapult_.estimated_position() -
+ catapult_.unprofiled_goal(0, 0)) < 0.05 &&
+ std::abs(catapult_.estimated_velocity()) < 0.5);
+ }
+
+ aos::Fetcher<frc971::control_loops::drivetrain::Status>
+ drivetrain_status_fetcher_;
+
+ aos::Fetcher<y2024::control_loops::superstructure::CANPosition>
+ superstructure_can_position_fetcher_;
+
+ const Constants *robot_constants_;
+
+ CatapultSubsystem catapult_;
+
+ PotAndAbsoluteEncoderSubsystem turret_;
+ PotAndAbsoluteEncoderSubsystem altitude_;
+
+ Aimer aimer_;
+
+ frc971::shooter_interpolation::InterpolationTable<
+ y2024::constants::Values::ShotParams>
+ interpolation_table_;
+
+ double last_retention_position_;
+
+ aos::monotonic_clock::time_point last_timestamp_{
+ aos::monotonic_clock::min_time};
+};
+
+} // namespace y2024::control_loops::superstructure
+
+#endif
diff --git a/y2024/control_loops/superstructure/superstructure.cc b/y2024/control_loops/superstructure/superstructure.cc
index 43a2453..5ac705b 100644
--- a/y2024/control_loops/superstructure/superstructure.cc
+++ b/y2024/control_loops/superstructure/superstructure.cc
@@ -35,7 +35,8 @@
robot_constants_->robot()->intake_constants()),
climber_(
robot_constants_->common()->climber(),
- robot_constants_->robot()->climber_constants()->zeroing_constants()) {
+ robot_constants_->robot()->climber_constants()->zeroing_constants()),
+ shooter_(event_loop, robot_constants_) {
event_loop->SetRuntimeRealtimePriority(30);
}
@@ -46,13 +47,11 @@
const monotonic_clock::time_point timestamp =
event_loop()->context().monotonic_event_time;
- (void)timestamp;
- (void)position;
-
if (WasReset()) {
AOS_LOG(ERROR, "WPILib reset, restarting\n");
intake_pivot_.Reset();
climber_.Reset();
+ shooter_.Reset();
}
OutputT output_struct;
@@ -175,14 +174,26 @@
output != nullptr ? &output_struct.climber_voltage : nullptr,
status->fbb());
+ const flatbuffers::Offset<ShooterStatus> shooter_status_offset =
+ shooter_.Iterate(
+ position,
+ unsafe_goal != nullptr ? unsafe_goal->shooter_goal() : nullptr,
+ output != nullptr ? &output_struct.catapult_voltage : nullptr,
+ output != nullptr ? &output_struct.altitude_voltage : nullptr,
+ output != nullptr ? &output_struct.turret_voltage : nullptr,
+ output != nullptr ? &output_struct.retention_roller_voltage : nullptr,
+ robot_state().voltage_battery(), timestamp, status->fbb());
+
if (output) {
output->CheckOk(output->Send(Output::Pack(*output->fbb(), &output_struct)));
}
Status::Builder status_builder = status->MakeBuilder<Status>();
- const bool zeroed = intake_pivot_.zeroed() && climber_.zeroed();
- const bool estopped = intake_pivot_.estopped() || climber_.estopped();
+ const bool zeroed =
+ intake_pivot_.zeroed() && climber_.zeroed() && shooter_.zeroed();
+ const bool estopped =
+ intake_pivot_.estopped() || climber_.estopped() || shooter_.estopped();
status_builder.add_zeroed(zeroed);
status_builder.add_estopped(estopped);
@@ -190,6 +201,7 @@
status_builder.add_intake_pivot(intake_pivot_status_offset);
status_builder.add_transfer_roller(transfer_roller_state);
status_builder.add_climber(climber_status_offset);
+ status_builder.add_shooter(shooter_status_offset);
(void)status->Send(status_builder.Finish());
}
diff --git a/y2024/control_loops/superstructure/superstructure.h b/y2024/control_loops/superstructure/superstructure.h
index f85e0fc..6c43863 100644
--- a/y2024/control_loops/superstructure/superstructure.h
+++ b/y2024/control_loops/superstructure/superstructure.h
@@ -11,6 +11,7 @@
#include "frc971/zeroing/pot_and_absolute_encoder.h"
#include "y2024/constants.h"
#include "y2024/constants/constants_generated.h"
+#include "y2024/control_loops/superstructure/shooter.h"
#include "y2024/control_loops/superstructure/superstructure_goal_generated.h"
#include "y2024/control_loops/superstructure/superstructure_output_generated.h"
#include "y2024/control_loops/superstructure/superstructure_position_generated.h"
@@ -43,6 +44,8 @@
return climber_;
}
+ inline const Shooter &shooter() const { return shooter_; }
+
double robot_velocity() const;
protected:
@@ -64,6 +67,8 @@
AbsoluteEncoderSubsystem intake_pivot_;
PotAndAbsoluteEncoderSubsystem climber_;
+ Shooter shooter_;
+
DISALLOW_COPY_AND_ASSIGN(Superstructure);
};
diff --git a/y2024/control_loops/superstructure/superstructure_goal.fbs b/y2024/control_loops/superstructure/superstructure_goal.fbs
index 4d46d1e..39f108a 100644
--- a/y2024/control_loops/superstructure/superstructure_goal.fbs
+++ b/y2024/control_loops/superstructure/superstructure_goal.fbs
@@ -46,6 +46,9 @@
// Position for the altitude when we aren't auto aiming
altitude_position: frc971.control_loops.StaticZeroingSingleDOFProfiledSubsystemGoal (id: 4);
+
+ // If true, we started with the ball loaded and should proceed to that state.
+ preloaded:bool = false (id: 5);
}
// Represents goal for extend
diff --git a/y2024/control_loops/superstructure/superstructure_lib_test.cc b/y2024/control_loops/superstructure/superstructure_lib_test.cc
index 78bbbdd..fbabcfb 100644
--- a/y2024/control_loops/superstructure/superstructure_lib_test.cc
+++ b/y2024/control_loops/superstructure/superstructure_lib_test.cc
@@ -5,6 +5,7 @@
#include "aos/events/logging/log_writer.h"
#include "frc971/control_loops/capped_test_plant.h"
+#include "frc971/control_loops/catapult/catapult_goal_static.h"
#include "frc971/control_loops/control_loop_test.h"
#include "frc971/control_loops/position_sensor_sim.h"
#include "frc971/control_loops/subsystem_simulator.h"
@@ -12,9 +13,12 @@
#include "frc971/zeroing/absolute_encoder.h"
#include "y2024/constants/simulated_constants_sender.h"
#include "y2024/control_loops/drivetrain/drivetrain_dog_motor_plant.h"
+#include "y2024/control_loops/superstructure/altitude/altitude_plant.h"
+#include "y2024/control_loops/superstructure/catapult/catapult_plant.h"
#include "y2024/control_loops/superstructure/climber/climber_plant.h"
#include "y2024/control_loops/superstructure/intake_pivot/intake_pivot_plant.h"
#include "y2024/control_loops/superstructure/superstructure.h"
+#include "y2024/control_loops/superstructure/turret/turret_plant.h"
DEFINE_string(output_folder, "",
"If set, logs all channels to the provided logfile.");
@@ -53,6 +57,8 @@
dt_(dt),
superstructure_position_sender_(
event_loop_->MakeSender<Position>("/superstructure")),
+ superstructure_can_position_sender_(
+ event_loop_->MakeSender<CANPosition>("/superstructure/rio")),
superstructure_status_fetcher_(
event_loop_->MakeFetcher<Status>("/superstructure")),
superstructure_output_fetcher_(
@@ -91,7 +97,67 @@
->climber_constants()
->zeroing_constants()
->measured_absolute_position(),
- dt_) {
+ dt_),
+ catapult_(new CappedTestPlant(catapult::MakeCatapultPlant()),
+ PositionSensorSimulator(simulated_robot_constants->robot()
+ ->catapult_constants()
+ ->zeroing_constants()
+ ->one_revolution_distance()),
+ {.subsystem_params =
+ {simulated_robot_constants->common()->catapult(),
+ simulated_robot_constants->robot()
+ ->catapult_constants()
+ ->zeroing_constants()},
+ .potentiometer_offset = simulated_robot_constants->robot()
+ ->catapult_constants()
+ ->potentiometer_offset()},
+ frc971::constants::Range::FromFlatbuffer(
+ simulated_robot_constants->common()->catapult()->range()),
+ simulated_robot_constants->robot()
+ ->catapult_constants()
+ ->zeroing_constants()
+ ->measured_absolute_position(),
+ dt_),
+ altitude_(new CappedTestPlant(altitude::MakeAltitudePlant()),
+ PositionSensorSimulator(simulated_robot_constants->robot()
+ ->altitude_constants()
+ ->zeroing_constants()
+ ->one_revolution_distance()),
+ {.subsystem_params =
+ {simulated_robot_constants->common()->altitude(),
+ simulated_robot_constants->robot()
+ ->altitude_constants()
+ ->zeroing_constants()},
+ .potentiometer_offset = simulated_robot_constants->robot()
+ ->altitude_constants()
+ ->potentiometer_offset()},
+ frc971::constants::Range::FromFlatbuffer(
+ simulated_robot_constants->common()->altitude()->range()),
+ simulated_robot_constants->robot()
+ ->altitude_constants()
+ ->zeroing_constants()
+ ->measured_absolute_position(),
+ dt_),
+ turret_(
+ new CappedTestPlant(turret::MakeTurretPlant()),
+ PositionSensorSimulator(simulated_robot_constants->robot()
+ ->turret_constants()
+ ->zeroing_constants()
+ ->one_revolution_distance()),
+ {.subsystem_params = {simulated_robot_constants->common()->turret(),
+ simulated_robot_constants->robot()
+ ->turret_constants()
+ ->zeroing_constants()},
+ .potentiometer_offset = simulated_robot_constants->robot()
+ ->turret_constants()
+ ->potentiometer_offset()},
+ frc971::constants::Range::FromFlatbuffer(
+ simulated_robot_constants->common()->turret()->range()),
+ simulated_robot_constants->robot()
+ ->turret_constants()
+ ->zeroing_constants()
+ ->measured_absolute_position(),
+ dt_) {
intake_pivot_.InitializePosition(
frc971::constants::Range::FromFlatbuffer(
simulated_robot_constants->common()->intake_pivot()->range())
@@ -100,6 +166,18 @@
frc971::constants::Range::FromFlatbuffer(
simulated_robot_constants->common()->climber()->range())
.middle());
+ catapult_.InitializePosition(
+ frc971::constants::Range::FromFlatbuffer(
+ simulated_robot_constants->common()->catapult()->range())
+ .middle());
+ altitude_.InitializePosition(
+ frc971::constants::Range::FromFlatbuffer(
+ simulated_robot_constants->common()->altitude()->range())
+ .middle());
+ turret_.InitializePosition(
+ frc971::constants::Range::FromFlatbuffer(
+ simulated_robot_constants->common()->turret()->range())
+ .middle());
phased_loop_handle_ = event_loop_->AddPhasedLoop(
[this](int) {
// Skip this the first time.
@@ -113,9 +191,21 @@
climber_.Simulate(superstructure_output_fetcher_->climber_voltage(),
superstructure_status_fetcher_->climber());
+ catapult_.Simulate(
+ superstructure_output_fetcher_->catapult_voltage(),
+ superstructure_status_fetcher_->shooter()->catapult());
+
+ altitude_.Simulate(
+ superstructure_output_fetcher_->altitude_voltage(),
+ superstructure_status_fetcher_->shooter()->altitude());
+
+ turret_.Simulate(
+ superstructure_output_fetcher_->turret_voltage(),
+ superstructure_status_fetcher_->shooter()->turret());
}
first_ = false;
SendPositionMessage();
+ SendCANPositionMessage();
},
dt);
}
@@ -132,25 +222,79 @@
frc971::PotAndAbsolutePosition::Builder climber_builder =
builder.MakeBuilder<frc971::PotAndAbsolutePosition>();
+
flatbuffers::Offset<frc971::PotAndAbsolutePosition> climber_offset =
climber_.encoder()->GetSensorValues(&climber_builder);
+ frc971::PotAndAbsolutePosition::Builder catapult_builder =
+ builder.MakeBuilder<frc971::PotAndAbsolutePosition>();
+
+ flatbuffers::Offset<frc971::PotAndAbsolutePosition> catapult_offset =
+ catapult_.encoder()->GetSensorValues(&catapult_builder);
+
+ frc971::PotAndAbsolutePosition::Builder altitude_builder =
+ builder.MakeBuilder<frc971::PotAndAbsolutePosition>();
+ flatbuffers::Offset<frc971::PotAndAbsolutePosition> altitude_offset =
+ altitude_.encoder()->GetSensorValues(&altitude_builder);
+
+ frc971::PotAndAbsolutePosition::Builder turret_builder =
+ builder.MakeBuilder<frc971::PotAndAbsolutePosition>();
+ flatbuffers::Offset<frc971::PotAndAbsolutePosition> turret_offset =
+ turret_.encoder()->GetSensorValues(&turret_builder);
Position::Builder position_builder = builder.MakeBuilder<Position>();
position_builder.add_transfer_beambreak(transfer_beambreak_);
position_builder.add_intake_pivot(intake_pivot_offset);
+ position_builder.add_catapult(catapult_offset);
+ position_builder.add_altitude(altitude_offset);
+ position_builder.add_turret(turret_offset);
position_builder.add_climber(climber_offset);
CHECK_EQ(builder.Send(position_builder.Finish()),
aos::RawSender::Error::kOk);
}
+ void SendCANPositionMessage() {
+ retention_position_ =
+ retention_velocity_ * std::chrono::duration<double>(dt_).count() +
+ retention_position_;
+
+ auto builder = superstructure_can_position_sender_.MakeBuilder();
+
+ frc971::control_loops::CANTalonFX::Builder retention_builder =
+ builder.MakeBuilder<frc971::control_loops::CANTalonFX>();
+
+ retention_builder.add_torque_current(retention_torque_current_);
+ retention_builder.add_position(retention_position_);
+
+ flatbuffers::Offset<frc971::control_loops::CANTalonFX> retention_offset =
+ retention_builder.Finish();
+
+ CANPosition::Builder can_position_builder =
+ builder.MakeBuilder<CANPosition>();
+
+ can_position_builder.add_retention_roller(retention_offset);
+
+ CHECK_EQ(builder.Send(can_position_builder.Finish()),
+ aos::RawSender::Error::kOk);
+ }
+
void set_transfer_beambreak(bool triggered) {
transfer_beambreak_ = triggered;
}
- AbsoluteEncoderSimulator *intake_pivot() { return &intake_pivot_; }
+ void set_retention_velocity(double velocity) {
+ retention_velocity_ = velocity;
+ }
+ void set_retention_torque_current(double torque_current) {
+ retention_torque_current_ = torque_current;
+ }
+
+ AbsoluteEncoderSimulator *intake_pivot() { return &intake_pivot_; }
+ PotAndAbsoluteEncoderSimulator *catapult() { return &catapult_; }
+ PotAndAbsoluteEncoderSimulator *altitude() { return &altitude_; }
+ PotAndAbsoluteEncoderSimulator *turret() { return &turret_; }
PotAndAbsoluteEncoderSimulator *climber() { return &climber_; }
private:
@@ -159,6 +303,7 @@
::aos::PhasedLoopHandler *phased_loop_handle_ = nullptr;
::aos::Sender<Position> superstructure_position_sender_;
+ ::aos::Sender<CANPosition> superstructure_can_position_sender_;
::aos::Fetcher<Status> superstructure_status_fetcher_;
::aos::Fetcher<Output> superstructure_output_fetcher_;
@@ -166,8 +311,15 @@
AbsoluteEncoderSimulator intake_pivot_;
PotAndAbsoluteEncoderSimulator climber_;
+ PotAndAbsoluteEncoderSimulator catapult_;
+ PotAndAbsoluteEncoderSimulator altitude_;
+ PotAndAbsoluteEncoderSimulator turret_;
bool first_ = true;
+
+ double retention_velocity_;
+ double retention_position_;
+ double retention_torque_current_;
};
class SuperstructureTest : public ::frc971::testing::ControlLoopTest {
@@ -199,6 +351,8 @@
test_event_loop_->MakeFetcher<Position>("/superstructure")),
superstructure_position_sender_(
test_event_loop_->MakeSender<Position>("/superstructure")),
+ superstructure_can_position_sender_(
+ test_event_loop_->MakeSender<CANPosition>("/superstructure/rio")),
drivetrain_status_sender_(
test_event_loop_->MakeSender<DrivetrainStatus>("/drivetrain")),
superstructure_plant_event_loop_(MakeEventLoop("plant", roborio_)),
@@ -244,6 +398,39 @@
superstructure_status_fetcher_->intake_pivot()->position(),
0.001);
+ if (superstructure_goal_fetcher_->has_shooter_goal()) {
+ if (superstructure_goal_fetcher_->shooter_goal()->has_turret_position() &&
+ !superstructure_goal_fetcher_->shooter_goal()->auto_aim()) {
+ EXPECT_NEAR(
+ superstructure_goal_fetcher_->shooter_goal()
+ ->turret_position()
+ ->unsafe_goal(),
+ superstructure_status_fetcher_->shooter()->turret()->position(),
+ 0.001);
+ }
+ }
+
+ if (superstructure_goal_fetcher_->has_shooter_goal()) {
+ if (superstructure_goal_fetcher_->shooter_goal()
+ ->has_altitude_position() &&
+ !superstructure_goal_fetcher_->shooter_goal()->auto_aim()) {
+ EXPECT_NEAR(
+ superstructure_goal_fetcher_->shooter_goal()
+ ->altitude_position()
+ ->unsafe_goal(),
+ superstructure_status_fetcher_->shooter()->altitude()->position(),
+ 0.001);
+ EXPECT_NEAR(superstructure_goal_fetcher_->shooter_goal()
+ ->altitude_position()
+ ->unsafe_goal(),
+ superstructure_plant_.altitude()->position(), 0.001);
+ }
+ }
+
+ EXPECT_NEAR(set_point,
+ superstructure_status_fetcher_->intake_pivot()->position(),
+ 0.001);
+
if (superstructure_status_fetcher_->intake_roller() ==
IntakeRollerStatus::NONE) {
EXPECT_EQ(superstructure_output_fetcher_->intake_roller_voltage(), 0.0);
@@ -291,6 +478,25 @@
!superstructure_status_fetcher_.get()->zeroed());
}
+ void WaitUntilNear(double turret_goal, double altitude_goal) {
+ int i = 0;
+ do {
+ i++;
+ RunFor(dt());
+ superstructure_status_fetcher_.Fetch();
+ // 2 Seconds
+
+ ASSERT_LE(i, 2.0 / ::aos::time::DurationInSeconds(dt()));
+
+ // Since there is a delay when sending running, make sure we have a
+ // status before checking it.
+ } while (superstructure_status_fetcher_.get() == nullptr ||
+ std::abs(superstructure_plant_.altitude()->position() -
+ altitude_goal) > 1e-3 ||
+ std::abs(superstructure_plant_.turret()->position() -
+ turret_goal) > 1e-3);
+ }
+
void SendRobotVelocity(double robot_velocity) {
SendDrivetrainStatus(robot_velocity, {0.0, 0.0}, 0.0);
}
@@ -329,6 +535,7 @@
::aos::Fetcher<Output> superstructure_output_fetcher_;
::aos::Fetcher<Position> superstructure_position_fetcher_;
::aos::Sender<Position> superstructure_position_sender_;
+ ::aos::Sender<CANPosition> superstructure_can_position_sender_;
::aos::Sender<DrivetrainStatus> drivetrain_status_sender_;
::std::unique_ptr<::aos::EventLoop> superstructure_plant_event_loop_;
@@ -347,12 +554,46 @@
SetEnabled(true);
WaitUntilZeroed();
+ superstructure_plant_.turret()->InitializePosition(
+ frc971::constants::Range::FromFlatbuffer(
+ simulated_robot_constants_->common()->turret()->range())
+ .middle());
+ superstructure_plant_.altitude()->InitializePosition(
+ frc971::constants::Range::FromFlatbuffer(
+ simulated_robot_constants_->common()->altitude()->range())
+ .middle());
+
{
auto builder = superstructure_goal_sender_.MakeBuilder();
+ flatbuffers::Offset<StaticZeroingSingleDOFProfiledSubsystemGoal>
+ turret_offset = CreateStaticZeroingSingleDOFProfiledSubsystemGoal(
+ *builder.fbb(),
+ frc971::constants::Range::FromFlatbuffer(
+ simulated_robot_constants_->common()->turret()->range())
+ .middle());
+
+ flatbuffers::Offset<StaticZeroingSingleDOFProfiledSubsystemGoal>
+ altitude_offset = CreateStaticZeroingSingleDOFProfiledSubsystemGoal(
+ *builder.fbb(),
+ frc971::constants::Range::FromFlatbuffer(
+ simulated_robot_constants_->common()->altitude()->range())
+ .middle());
+
+ ShooterGoal::Builder shooter_goal_builder =
+ builder.MakeBuilder<ShooterGoal>();
+
+ shooter_goal_builder.add_turret_position(turret_offset);
+ shooter_goal_builder.add_altitude_position(altitude_offset);
+ shooter_goal_builder.add_auto_aim(false);
+
+ flatbuffers::Offset<ShooterGoal> shooter_goal_offset =
+ shooter_goal_builder.Finish();
+
Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
goal_builder.add_intake_pivot_goal(IntakePivotGoal::RETRACTED);
goal_builder.add_climber_goal(ClimberGoal::RETRACT);
+ goal_builder.add_shooter_goal(shooter_goal_offset);
ASSERT_EQ(builder.Send(goal_builder.Finish()), aos::RawSender::Error::kOk);
}
@@ -369,18 +610,56 @@
superstructure_plant_.intake_pivot()->InitializePosition(
frc971::constants::Range::FromFlatbuffer(
simulated_robot_constants_->common()->intake_pivot()->range())
+ .lower);
+
+ superstructure_plant_.turret()->InitializePosition(
+ frc971::constants::Range::FromFlatbuffer(
+ simulated_robot_constants_->common()->turret()->range())
+ .lower);
+
+ superstructure_plant_.altitude()->InitializePosition(
+ frc971::constants::Range::FromFlatbuffer(
+ simulated_robot_constants_->common()->altitude()->range())
.middle());
+
superstructure_plant_.climber()->InitializePosition(
frc971::constants::Range::FromFlatbuffer(
simulated_robot_constants_->common()->climber()->range())
.lower);
+
WaitUntilZeroed();
+
{
auto builder = superstructure_goal_sender_.MakeBuilder();
+ flatbuffers::Offset<StaticZeroingSingleDOFProfiledSubsystemGoal>
+ turret_offset = CreateStaticZeroingSingleDOFProfiledSubsystemGoal(
+ *builder.fbb(),
+ frc971::constants::Range::FromFlatbuffer(
+ simulated_robot_constants_->common()->turret()->range())
+ .upper);
+
+ flatbuffers::Offset<StaticZeroingSingleDOFProfiledSubsystemGoal>
+ altitude_offset = CreateStaticZeroingSingleDOFProfiledSubsystemGoal(
+ *builder.fbb(),
+ frc971::constants::Range::FromFlatbuffer(
+ simulated_robot_constants_->common()->altitude()->range())
+ .upper);
+
+ ShooterGoal::Builder shooter_goal_builder =
+ builder.MakeBuilder<ShooterGoal>();
+
+ shooter_goal_builder.add_turret_position(turret_offset);
+ shooter_goal_builder.add_altitude_position(altitude_offset);
+ shooter_goal_builder.add_auto_aim(false);
+
+ flatbuffers::Offset<ShooterGoal> shooter_goal_offset =
+ shooter_goal_builder.Finish();
+
Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
goal_builder.add_intake_pivot_goal(IntakePivotGoal::EXTENDED);
goal_builder.add_climber_goal(ClimberGoal::FULL_EXTEND);
+ goal_builder.add_shooter_goal(shooter_goal_offset);
ASSERT_EQ(builder.Send(goal_builder.Finish()), aos::RawSender::Error::kOk);
}
@@ -401,9 +680,34 @@
{
auto builder = superstructure_goal_sender_.MakeBuilder();
+ flatbuffers::Offset<StaticZeroingSingleDOFProfiledSubsystemGoal>
+ turret_offset = CreateStaticZeroingSingleDOFProfiledSubsystemGoal(
+ *builder.fbb(),
+ frc971::constants::Range::FromFlatbuffer(
+ simulated_robot_constants_->common()->turret()->range())
+ .upper);
+
+ flatbuffers::Offset<StaticZeroingSingleDOFProfiledSubsystemGoal>
+ altitude_offset = CreateStaticZeroingSingleDOFProfiledSubsystemGoal(
+ *builder.fbb(),
+ frc971::constants::Range::FromFlatbuffer(
+ simulated_robot_constants_->common()->altitude()->range())
+ .upper);
+
+ ShooterGoal::Builder shooter_goal_builder =
+ builder.MakeBuilder<ShooterGoal>();
+
+ shooter_goal_builder.add_turret_position(turret_offset);
+ shooter_goal_builder.add_altitude_position(altitude_offset);
+ shooter_goal_builder.add_auto_aim(false);
+
+ flatbuffers::Offset<ShooterGoal> shooter_goal_offset =
+ shooter_goal_builder.Finish();
+
Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
goal_builder.add_intake_pivot_goal(IntakePivotGoal::EXTENDED);
goal_builder.add_climber_goal(ClimberGoal::FULL_EXTEND);
+ goal_builder.add_shooter_goal(shooter_goal_offset);
ASSERT_EQ(builder.Send(goal_builder.Finish()), aos::RawSender::Error::kOk);
}
@@ -415,9 +719,36 @@
{
auto builder = superstructure_goal_sender_.MakeBuilder();
+ flatbuffers::Offset<StaticZeroingSingleDOFProfiledSubsystemGoal>
+ turret_offset = CreateStaticZeroingSingleDOFProfiledSubsystemGoal(
+ *builder.fbb(),
+ frc971::constants::Range::FromFlatbuffer(
+ simulated_robot_constants_->common()->turret()->range())
+ .lower,
+ CreateProfileParameters(*builder.fbb(), 20.0, 10));
+
+ flatbuffers::Offset<StaticZeroingSingleDOFProfiledSubsystemGoal>
+ altitude_offset = CreateStaticZeroingSingleDOFProfiledSubsystemGoal(
+ *builder.fbb(),
+ frc971::constants::Range::FromFlatbuffer(
+ simulated_robot_constants_->common()->altitude()->range())
+ .lower,
+ CreateProfileParameters(*builder.fbb(), 20.0, 10));
+
+ ShooterGoal::Builder shooter_goal_builder =
+ builder.MakeBuilder<ShooterGoal>();
+
+ shooter_goal_builder.add_turret_position(turret_offset);
+ shooter_goal_builder.add_altitude_position(altitude_offset);
+ shooter_goal_builder.add_auto_aim(false);
+
+ flatbuffers::Offset<ShooterGoal> shooter_goal_offset =
+ shooter_goal_builder.Finish();
+
Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
goal_builder.add_intake_pivot_goal(IntakePivotGoal::RETRACTED);
goal_builder.add_climber_goal(ClimberGoal::RETRACT);
+ goal_builder.add_shooter_goal(shooter_goal_offset);
ASSERT_EQ(builder.Send(goal_builder.Finish()), aos::RawSender::Error::kOk);
}
@@ -437,6 +768,12 @@
EXPECT_EQ(PotAndAbsoluteEncoderSubsystem::State::RUNNING,
superstructure_.climber().state());
+
+ EXPECT_EQ(PotAndAbsoluteEncoderSubsystem::State::RUNNING,
+ superstructure_.shooter().turret().state());
+
+ EXPECT_EQ(PotAndAbsoluteEncoderSubsystem::State::RUNNING,
+ superstructure_.shooter().altitude().state());
}
// Tests that running disabled works
@@ -603,4 +940,399 @@
EXPECT_EQ(superstructure_output_fetcher_->transfer_roller_voltage(), 0.0);
}
+
+// Tests the full range of activities we need to be doing from loading ->
+// shooting
+TEST_F(SuperstructureTest, LoadingToShooting) {
+ SetEnabled(true);
+
+ WaitUntilZeroed();
+
+ constexpr double kTurretGoal = 2.0;
+ constexpr double kAltitudeGoal = 0.5;
+
+ set_alliance(aos::Alliance::kRed);
+ SendDrivetrainStatus(0.0, {0.0, 5.0}, 0.0);
+
+ // Auto aim, but don't fire.
+ {
+ auto builder = superstructure_goal_sender_.MakeBuilder();
+
+ ShooterGoal::Builder shooter_goal_builder =
+ builder.MakeBuilder<ShooterGoal>();
+
+ shooter_goal_builder.add_auto_aim(true);
+ shooter_goal_builder.add_fire(false);
+
+ flatbuffers::Offset<ShooterGoal> shooter_goal_offset =
+ shooter_goal_builder.Finish();
+
+ Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
+
+ goal_builder.add_intake_pivot_goal(IntakePivotGoal::RETRACTED);
+ goal_builder.add_shooter_goal(shooter_goal_offset);
+
+ ASSERT_EQ(builder.Send(goal_builder.Finish()), aos::RawSender::Error::kOk);
+ }
+
+ superstructure_plant_.set_retention_velocity(5);
+ superstructure_plant_.set_retention_torque_current(1);
+ superstructure_plant_.set_transfer_beambreak(false);
+
+ RunFor(chrono::seconds(5));
+
+ VerifyNearGoal();
+
+ EXPECT_NEAR(superstructure_status_fetcher_->shooter()->turret()->position(),
+ simulated_robot_constants_->common()->turret_loading_position(),
+ 0.01);
+
+ EXPECT_NEAR(superstructure_status_fetcher_->shooter()->altitude()->position(),
+ 0.0, 0.01);
+
+ EXPECT_EQ(superstructure_status_fetcher_->shooter()->catapult_state(),
+ CatapultState::READY);
+
+ // Now, extend the intake.
+ {
+ auto builder = superstructure_goal_sender_.MakeBuilder();
+
+ ShooterGoal::Builder shooter_goal_builder =
+ builder.MakeBuilder<ShooterGoal>();
+
+ shooter_goal_builder.add_auto_aim(true);
+ shooter_goal_builder.add_fire(false);
+
+ flatbuffers::Offset<ShooterGoal> shooter_goal_offset =
+ shooter_goal_builder.Finish();
+
+ Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
+
+ goal_builder.add_intake_pivot_goal(IntakePivotGoal::EXTENDED);
+ goal_builder.add_intake_roller_goal(IntakeRollerGoal::INTAKE);
+ goal_builder.add_shooter_goal(shooter_goal_offset);
+
+ ASSERT_EQ(builder.Send(goal_builder.Finish()), aos::RawSender::Error::kOk);
+ }
+
+ superstructure_plant_.set_transfer_beambreak(false);
+
+ RunFor(chrono::seconds(5));
+
+ VerifyNearGoal();
+
+ EXPECT_NEAR(superstructure_status_fetcher_->shooter()->turret()->position(),
+ simulated_robot_constants_->common()->turret_loading_position(),
+ 0.01);
+
+ EXPECT_NEAR(superstructure_status_fetcher_->shooter()->altitude()->position(),
+ 0.0, 0.01);
+
+ EXPECT_EQ(superstructure_status_fetcher_->shooter()->catapult_state(),
+ CatapultState::READY);
+
+ // Signal through the transfer roller that we got it.
+ superstructure_plant_.set_transfer_beambreak(true);
+
+ RunFor(chrono::seconds(5));
+
+ VerifyNearGoal();
+
+ EXPECT_EQ(superstructure_output_fetcher_->transfer_roller_voltage(), 0.0);
+
+ // Verify we are in the loading position.
+ EXPECT_NEAR(superstructure_status_fetcher_->shooter()->turret()->position(),
+ simulated_robot_constants_->common()->turret_loading_position(),
+ 0.01);
+
+ EXPECT_NEAR(superstructure_status_fetcher_->shooter()->altitude()->position(),
+ 0.0, 0.01);
+
+ EXPECT_EQ(superstructure_status_fetcher_->shooter()->catapult_state(),
+ CatapultState::READY);
+
+ // Set retention roller to show that we are loaded.
+ superstructure_plant_.set_retention_velocity(0.1);
+ superstructure_plant_.set_retention_torque_current(45);
+ superstructure_plant_.set_transfer_beambreak(true);
+
+ RunFor(chrono::seconds(5));
+
+ VerifyNearGoal();
+
+ // Should now be loaded.
+ EXPECT_EQ(superstructure_output_fetcher_->transfer_roller_voltage(), 0.0);
+
+ EXPECT_NEAR(superstructure_status_fetcher_->shooter()->altitude()->position(),
+ simulated_robot_constants_->common()->altitude_loading_position(),
+ 0.01);
+
+ EXPECT_EQ(superstructure_status_fetcher_->shooter()->catapult_state(),
+ CatapultState::LOADED);
+
+ // Fire. Start by triggering a motion and then firing all in 1 go.
+ {
+ auto builder = superstructure_goal_sender_.MakeBuilder();
+
+ flatbuffers::Offset<StaticZeroingSingleDOFProfiledSubsystemGoal>
+ turret_offset = CreateStaticZeroingSingleDOFProfiledSubsystemGoal(
+ *builder.fbb(), kTurretGoal);
+
+ flatbuffers::Offset<StaticZeroingSingleDOFProfiledSubsystemGoal>
+ altitude_offset = CreateStaticZeroingSingleDOFProfiledSubsystemGoal(
+ *builder.fbb(), kAltitudeGoal);
+
+ auto catapult_builder =
+ builder.MakeBuilder<frc971::control_loops::catapult::CatapultGoal>();
+ catapult_builder.add_shot_velocity(15.0);
+
+ const auto catapult_offset = catapult_builder.Finish();
+
+ ShooterGoal::Builder shooter_goal_builder =
+ builder.MakeBuilder<ShooterGoal>();
+
+ shooter_goal_builder.add_auto_aim(false);
+ shooter_goal_builder.add_fire(true);
+ shooter_goal_builder.add_catapult_goal(catapult_offset);
+ shooter_goal_builder.add_altitude_position(altitude_offset);
+ shooter_goal_builder.add_turret_position(turret_offset);
+
+ flatbuffers::Offset<ShooterGoal> shooter_goal_offset =
+ shooter_goal_builder.Finish();
+
+ Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
+
+ goal_builder.add_intake_pivot_goal(IntakePivotGoal::RETRACTED);
+ goal_builder.add_shooter_goal(shooter_goal_offset);
+
+ ASSERT_EQ(builder.Send(goal_builder.Finish()), aos::RawSender::Error::kOk);
+ }
+
+ superstructure_plant_.set_transfer_beambreak(false);
+
+ // Wait until the bot finishes auto-aiming.
+ WaitUntilNear(kTurretGoal, kAltitudeGoal);
+
+ RunFor(chrono::milliseconds(10));
+
+ // Make sure it stays at firing for a bit.
+ EXPECT_EQ(superstructure_status_fetcher_->shooter()->catapult_state(),
+ CatapultState::FIRING);
+
+ // Wheel should spin free again.
+ superstructure_plant_.set_retention_velocity(5);
+ superstructure_plant_.set_retention_torque_current(1);
+
+ RunFor(chrono::seconds(5));
+
+ // And we should be back to ready.
+ CHECK(superstructure_status_fetcher_.Fetch());
+ EXPECT_EQ(superstructure_status_fetcher_->shooter()->catapult_state(),
+ CatapultState::READY);
+
+ VerifyNearGoal();
+
+ EXPECT_NEAR(superstructure_status_fetcher_->shooter()->turret()->position(),
+ kTurretGoal, 0.001);
+
+ EXPECT_NEAR(superstructure_status_fetcher_->shooter()->altitude()->position(),
+ kAltitudeGoal, 0.001);
+
+ {
+ auto builder = superstructure_goal_sender_.MakeBuilder();
+
+ ShooterGoal::Builder shooter_goal_builder =
+ builder.MakeBuilder<ShooterGoal>();
+
+ shooter_goal_builder.add_auto_aim(false);
+ shooter_goal_builder.add_fire(false);
+
+ flatbuffers::Offset<ShooterGoal> shooter_goal_offset =
+ shooter_goal_builder.Finish();
+
+ Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
+
+ goal_builder.add_shooter_goal(shooter_goal_offset);
+
+ ASSERT_EQ(builder.Send(goal_builder.Finish()), aos::RawSender::Error::kOk);
+ }
+
+ superstructure_plant_.set_retention_velocity(5);
+ superstructure_plant_.set_retention_torque_current(45);
+ superstructure_plant_.set_transfer_beambreak(false);
+
+ RunFor(chrono::seconds(5));
+
+ VerifyNearGoal();
+
+ EXPECT_EQ(superstructure_status_fetcher_->shooter()->catapult_state(),
+ CatapultState::READY);
+
+ EXPECT_NEAR(superstructure_status_fetcher_->shooter()->turret()->position(),
+ simulated_robot_constants_->common()->turret_loading_position(),
+ 0.01);
+
+ EXPECT_NEAR(superstructure_status_fetcher_->shooter()->altitude()->position(),
+ simulated_robot_constants_->common()->altitude_loading_position(),
+ 0.01);
+}
+
+// Test that we are able to signal that the note was preloaded
+TEST_F(SuperstructureTest, Preloaded) {
+ // Put the bucket at the starting position.
+ superstructure_plant_.catapult()->InitializePosition(
+ simulated_robot_constants_->common()->catapult_return_position());
+
+ SetEnabled(true);
+ WaitUntilZeroed();
+
+ {
+ auto builder = superstructure_goal_sender_.MakeBuilder();
+
+ ShooterGoal::Builder shooter_goal_builder =
+ builder.MakeBuilder<ShooterGoal>();
+
+ shooter_goal_builder.add_preloaded(true);
+
+ flatbuffers::Offset<ShooterGoal> shooter_goal_offset =
+ shooter_goal_builder.Finish();
+
+ Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
+
+ goal_builder.add_shooter_goal(shooter_goal_offset);
+
+ ASSERT_EQ(builder.Send(goal_builder.Finish()), aos::RawSender::Error::kOk);
+ }
+
+ RunFor(dt());
+
+ superstructure_status_fetcher_.Fetch();
+ ASSERT_TRUE(superstructure_status_fetcher_.get() != nullptr);
+
+ EXPECT_EQ(superstructure_status_fetcher_->shooter()->catapult_state(),
+ CatapultState::LOADED);
+}
+
+// Tests that auto aim works properly for the shooter
+TEST_F(SuperstructureTest, AutoAim) {
+ superstructure_plant_.catapult()->InitializePosition(
+ simulated_robot_constants_->common()->catapult_return_position());
+ SetEnabled(true);
+ WaitUntilZeroed();
+
+ constexpr double kDistanceFromSpeaker = 5.0;
+
+ const double kRedSpeakerX = simulated_robot_constants_->common()
+ ->shooter_targets()
+ ->red_alliance()
+ ->pos()
+ ->data()
+ ->Get(0);
+ const double kRedSpeakerY = simulated_robot_constants_->common()
+ ->shooter_targets()
+ ->red_alliance()
+ ->pos()
+ ->data()
+ ->Get(1);
+
+ const double kBlueSpeakerX = simulated_robot_constants_->common()
+ ->shooter_targets()
+ ->blue_alliance()
+ ->pos()
+ ->data()
+ ->Get(0);
+ const double kBlueSpeakerY = simulated_robot_constants_->common()
+ ->shooter_targets()
+ ->blue_alliance()
+ ->pos()
+ ->data()
+ ->Get(1);
+
+ set_alliance(aos::Alliance::kRed);
+ // Set the robot facing 90 degrees away from the speaker
+ SendDrivetrainStatus(
+ 0.0, {(kRedSpeakerX - kDistanceFromSpeaker), kRedSpeakerY}, M_PI_2);
+
+ {
+ auto builder = superstructure_goal_sender_.MakeBuilder();
+
+ ShooterGoal::Builder shooter_goal_builder =
+ builder.MakeBuilder<ShooterGoal>();
+
+ shooter_goal_builder.add_auto_aim(true);
+ shooter_goal_builder.add_preloaded(true);
+
+ flatbuffers::Offset<ShooterGoal> shooter_goal_offset =
+ shooter_goal_builder.Finish();
+
+ Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
+
+ goal_builder.add_shooter_goal(shooter_goal_offset);
+
+ ASSERT_EQ(builder.Send(goal_builder.Finish()), aos::RawSender::Error::kOk);
+ }
+
+ superstructure_plant_.set_retention_velocity(0);
+ superstructure_plant_.set_retention_torque_current(45);
+
+ RunFor(chrono::seconds(5));
+
+ VerifyNearGoal();
+
+ EXPECT_NEAR(
+ -M_PI_2,
+ superstructure_status_fetcher_->shooter()->aimer()->turret_position(),
+ 5e-4);
+ EXPECT_NEAR(-M_PI_2,
+ superstructure_status_fetcher_->shooter()->turret()->position(),
+ 5e-4);
+ LOG(INFO) << aos::FlatbufferToJson(superstructure_status_fetcher_.get(),
+ {.multi_line = true});
+
+ EXPECT_EQ(
+ kDistanceFromSpeaker,
+ superstructure_status_fetcher_->shooter()->aimer()->target_distance());
+
+ set_alliance(aos::Alliance::kBlue);
+ // Set the robot facing 90 degrees away from the speaker
+ SendDrivetrainStatus(
+ 0.0, {(kBlueSpeakerX + kDistanceFromSpeaker), kBlueSpeakerY}, M_PI_2);
+
+ {
+ auto builder = superstructure_goal_sender_.MakeBuilder();
+
+ ShooterGoal::Builder shooter_goal_builder =
+ builder.MakeBuilder<ShooterGoal>();
+
+ shooter_goal_builder.add_auto_aim(true);
+
+ flatbuffers::Offset<ShooterGoal> shooter_goal_offset =
+ shooter_goal_builder.Finish();
+
+ Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
+
+ goal_builder.add_shooter_goal(shooter_goal_offset);
+
+ ASSERT_EQ(builder.Send(goal_builder.Finish()), aos::RawSender::Error::kOk);
+ }
+
+ superstructure_plant_.set_retention_velocity(0);
+ superstructure_plant_.set_retention_torque_current(45);
+
+ RunFor(chrono::seconds(5));
+
+ VerifyNearGoal();
+
+ EXPECT_NEAR(
+ M_PI_2,
+ superstructure_status_fetcher_->shooter()->aimer()->turret_position(),
+ 5e-4);
+ EXPECT_NEAR(M_PI_2,
+ superstructure_status_fetcher_->shooter()->turret()->position(),
+ 5e-4);
+ EXPECT_EQ(
+ kDistanceFromSpeaker,
+ superstructure_status_fetcher_->shooter()->aimer()->target_distance());
+}
+
} // namespace y2024::control_loops::superstructure::testing
diff --git a/y2024/control_loops/superstructure/superstructure_status.fbs b/y2024/control_loops/superstructure/superstructure_status.fbs
index bda9bbf..7b7912e 100644
--- a/y2024/control_loops/superstructure/superstructure_status.fbs
+++ b/y2024/control_loops/superstructure/superstructure_status.fbs
@@ -10,13 +10,27 @@
INTAKING = 2,
}
-enum CatapultStatus: ubyte {
+enum CatapultState: ubyte {
// Means we are waiting for a game piece
- IDLE = 0,
+ READY = 0,
// Means we have a game piece
LOADED = 1,
// Means we are firing a game piece
FIRING = 2,
+ // We are retracting the bucket to do it again.
+ RETRACTING = 3,
+}
+
+table AimerStatus {
+ // The current goal angle for the turret auto-tracking, in radians.
+ turret_position:double (id: 0);
+ // The current goal velocity for the turret, in radians / sec.
+ turret_velocity:double (id: 1);
+ // The current distance to the target, in meters.
+ target_distance:double (id: 2);
+ // The current "shot distance." When shooting on the fly, this may be
+ // different from the static distance to the target.
+ shot_distance:double (id: 3);
}
table ShooterStatus {
@@ -29,7 +43,10 @@
// Estimated angle and angular velocitiy of the altitude.
altitude:frc971.control_loops.PotAndAbsoluteEncoderProfiledJointStatus (id: 2);
- catapult_state: CatapultStatus (id: 3);
+ catapult_state: CatapultState (id: 3);
+
+ // Status of the aimer
+ aimer:AimerStatus (id: 4);
}
// Contains status of transfer rollers