Converted hood to only need an index pulse.
We now have a more complicated seek mechanism which runs the hood to
each hard stop, or until we find both index pulses.
Change-Id: I9932cc158beec0bc55dc0e908accb0aea6a73506
diff --git a/frc971/control_loops/profiled_subsystem.h b/frc971/control_loops/profiled_subsystem.h
index 45ef912..031795a 100644
--- a/frc971/control_loops/profiled_subsystem.h
+++ b/frc971/control_loops/profiled_subsystem.h
@@ -172,11 +172,12 @@
const ::frc971::constants::Range &range() const { return range_; }
- private:
+ protected:
// Limits the provided goal to the soft limits. Prints "name" when it fails
// to aid debugging.
- void CapGoal(const char *name, Eigen::Matrix<double, 3, 1> *goal);
+ virtual void CapGoal(const char *name, Eigen::Matrix<double, 3, 1> *goal);
+ private:
void UpdateOffset(double offset);
aos::util::TrapezoidProfile profile_;
diff --git a/frc971/control_loops/profiled_subsystem.q b/frc971/control_loops/profiled_subsystem.q
index 2bc1879..f89da14 100644
--- a/frc971/control_loops/profiled_subsystem.q
+++ b/frc971/control_loops/profiled_subsystem.q
@@ -77,3 +77,41 @@
// State of the estimator.
.frc971.AbsoluteEstimatorState estimator_state;
};
+
+struct IndexProfiledJointStatus {
+ // Is the subsystem zeroed?
+ bool zeroed;
+
+ // The state of the subsystem, if applicable. -1 otherwise.
+ int32_t state;
+
+ // If true, we have aborted.
+ bool estopped;
+
+ // Position of the joint.
+ float position;
+ // Velocity of the joint in units/second.
+ float velocity;
+ // Profiled goal position of the joint.
+ float goal_position;
+ // Profiled goal velocity of the joint in units/second.
+ float goal_velocity;
+ // Unprofiled goal position from absoulte zero of the joint.
+ float unprofiled_goal_position;
+ // Unprofiled goal velocity of the joint in units/second.
+ float unprofiled_goal_velocity;
+
+ // The estimated voltage error.
+ float voltage_error;
+
+ // The calculated velocity with delta x/delta t
+ float calculated_velocity;
+
+ // Components of the control loop output
+ float position_power;
+ float velocity_power;
+ float feedforwards_power;
+
+ // State of the estimator.
+ .frc971.IndexEstimatorState estimator_state;
+};
diff --git a/frc971/zeroing/zeroing.h b/frc971/zeroing/zeroing.h
index 62d346e..ac2e49e 100644
--- a/frc971/zeroing/zeroing.h
+++ b/frc971/zeroing/zeroing.h
@@ -196,7 +196,7 @@
class PulseIndexZeroingEstimator : public ZeroingEstimator {
public:
using Position = IndexPosition;
- using ZeroingConstants = constants::PotAndIndexPulseZeroingConstants;
+ using ZeroingConstants = constants::EncoderPlusIndexZeroingConstants;
using State = IndexEstimatorState;
PulseIndexZeroingEstimator(
@@ -210,6 +210,9 @@
bool zeroed() const override { return zeroed_; }
+ // It's as ready as it'll ever be...
+ bool offset_ready() const { return true; }
+
double offset() const override { return offset_; }
bool error() const override { return error_; }
diff --git a/y2017/analysis/hood_test b/y2017/analysis/hood_test
index 3bbc0f0..2d88970 100644
--- a/y2017/analysis/hood_test
+++ b/y2017/analysis/hood_test
@@ -2,5 +2,11 @@
superstructure_lib_test status hood estimator_state position
superstructure_lib_test status hood state
+superstructure_lib_test status hood position
+superstructure_lib_test status hood velocity
+superstructure_lib_test status hood goal_position
+superstructure_lib_test status hood goal_velocity
+
+superstructure_lib_test status hood unprofiled_goal_position
superstructure_lib_test goal hood angle
-superstructure_lib_test position hood pot
+superstructure_lib_test position hood encoder
diff --git a/y2017/constants.cc b/y2017/constants.cc
index c716ecf..2ac4875 100644
--- a/y2017/constants.cc
+++ b/y2017/constants.cc
@@ -71,45 +71,59 @@
intake->zeroing.average_filter_size = Values::kZeroingSampleSize;
intake->zeroing.one_revolution_distance = Values::kIntakeEncoderIndexDifference;
- intake->zeroing.measured_absolute_position = 0;
intake->zeroing.zeroing_threshold = 0.001;
intake->zeroing.moving_buffer_size = 9;
intake->zeroing.allowable_encoder_error = 0.3;
turret->zeroing.average_filter_size = Values::kZeroingSampleSize;
- turret->zeroing.one_revolution_distance = Values::kTurretEncoderIndexDifference;
- turret->zeroing.measured_absolute_position = 0;
+ turret->zeroing.one_revolution_distance =
+ Values::kTurretEncoderIndexDifference;
turret->zeroing.zeroing_threshold = 0.001;
turret->zeroing.moving_buffer_size = 9;
turret->zeroing.allowable_encoder_error = 0.3;
- hood->zeroing.average_filter_size = Values::kZeroingSampleSize;
+ hood->zeroing.index_pulse_count = 2;
hood->zeroing.index_difference = Values::kHoodEncoderIndexDifference;
- hood->zeroing.measured_index_position = 0.1;
- hood->zeroing.allowable_encoder_error = 0.3;
+ hood->zeroing.known_index_pulse = 0;
switch (team) {
// A set of constants for tests.
case 1:
intake->pot_offset = 0;
+ intake->zeroing.measured_absolute_position = 0;
+
turret->pot_offset = 0;
+ turret->zeroing.measured_absolute_position = 0;
+
hood->pot_offset = 0.1;
+ hood->zeroing.measured_index_position = 0.05;
+
r->down_error = 0;
r->vision_name = "test";
break;
case kCompTeamNumber:
intake->pot_offset = 0;
+ intake->zeroing.measured_absolute_position = 0;
+
turret->pot_offset = 0;
- hood->pot_offset = 0.1;
+ turret->zeroing.measured_absolute_position = 0;
+
+ hood->zeroing.measured_index_position = 0.05;
+
r->down_error = 0;
r->vision_name = "competition";
break;
case kPracticeTeamNumber:
intake->pot_offset = 0;
+ intake->zeroing.measured_absolute_position = 0;
+
turret->pot_offset = 0;
- hood->pot_offset = 0.1;
+ turret->zeroing.measured_absolute_position = 0;
+
+ hood->zeroing.measured_index_position = 0.05;
+
r->down_error = 0;
r->vision_name = "practice";
break;
diff --git a/y2017/constants.h b/y2017/constants.h
index 1ad582b..5305367 100644
--- a/y2017/constants.h
+++ b/y2017/constants.h
@@ -39,7 +39,7 @@
struct Hood {
double pot_offset;
- ::frc971::constants::PotAndIndexPulseZeroingConstants zeroing;
+ ::frc971::constants::EncoderPlusIndexZeroingConstants zeroing;
};
static const int kZeroingSampleSize = 200;
diff --git a/y2017/control_loops/python/hood.py b/y2017/control_loops/python/hood.py
index 8684c98..40d3473 100755
--- a/y2017/control_loops/python/hood.py
+++ b/y2017/control_loops/python/hood.py
@@ -157,14 +157,14 @@
self.A, self.B = self.ContinuousToDiscrete(
self.A_continuous, self.B_continuous, self.dt)
- q_pos = 0.12
- q_vel = 2.00
- q_voltage = 3.0
+ q_pos = 0.01
+ q_vel = 4.0
+ q_voltage = 4.0
self.Q = numpy.matrix([[(q_pos ** 2.0), 0.0, 0.0],
[0.0, (q_vel ** 2.0), 0.0],
[0.0, 0.0, (q_voltage ** 2.0)]])
- r_pos = 0.05
+ r_pos = 0.001
self.R = numpy.matrix([[(r_pos ** 2.0)]])
self.KalmanGain, self.Q_steady = controls.kalman(
@@ -186,6 +186,7 @@
self.t = []
self.x = []
self.v = []
+ self.v_hat = []
self.a = []
self.x_hat = []
self.u = []
@@ -251,6 +252,7 @@
self.v.append(hood.X[1, 0])
self.a.append((self.v[-1] - last_v) / hood.dt)
+ self.v_hat.append(observer_hood.X_hat[1, 0])
offset = 0.0
if i > 100:
@@ -277,6 +279,8 @@
pylab.subplot(3, 1, 1)
pylab.plot(self.t, self.x, label='x')
pylab.plot(self.t, self.x_hat, label='x_hat')
+ pylab.plot(self.t, self.v, label='v')
+ pylab.plot(self.t, self.v_hat, label='v_hat')
pylab.legend()
pylab.subplot(3, 1, 2)
@@ -301,7 +305,7 @@
# Test moving the hood with constant separation.
initial_X = numpy.matrix([[0.0], [0.0]])
- R = numpy.matrix([[numpy.pi/2.0], [0.0], [0.0]])
+ R = numpy.matrix([[numpy.pi/4.0], [0.0], [0.0]])
scenario_plotter.run_test(hood, end_goal=R,
controller_hood=hood_controller,
observer_hood=observer_hood, iterations=200)
diff --git a/y2017/control_loops/superstructure/hood/hood.cc b/y2017/control_loops/superstructure/hood/hood.cc
index 8af4a9e..b5acba0 100644
--- a/y2017/control_loops/superstructure/hood/hood.cc
+++ b/y2017/control_loops/superstructure/hood/hood.cc
@@ -10,9 +10,13 @@
constexpr double Hood::kZeroingVoltage;
constexpr double Hood::kOperatingVoltage;
+// The tracking error to allow before declaring that we are stuck and reversing
+// direction while zeroing.
+constexpr double kStuckZeroingTrackingError = 0.02;
-Hood::Hood()
- : profiled_subsystem_(
+IndexPulseProfiledSubsystem::IndexPulseProfiledSubsystem()
+ : ::frc971::control_loops::SingleDOFProfiledSubsystem<
+ ::frc971::zeroing::PulseIndexZeroingEstimator>(
::std::unique_ptr<
::frc971::control_loops::SimpleCappedStateFeedbackLoop<3, 1, 1>>(
new ::frc971::control_loops::SimpleCappedStateFeedbackLoop<
@@ -20,15 +24,40 @@
constants::GetValues().hood.zeroing, constants::Values::kHoodRange,
0.5, 10.0) {}
+void IndexPulseProfiledSubsystem::CapGoal(const char *name,
+ Eigen::Matrix<double, 3, 1> *goal) {
+ if (zeroed()) {
+ ::frc971::control_loops::SingleDOFProfiledSubsystem<
+ ::frc971::zeroing::PulseIndexZeroingEstimator>::CapGoal(name, goal);
+ } else {
+ const double kMaxRange = range().upper_hard - range().lower_hard;
+ // Limit the goal to min/max allowable positions much less agressively.
+ // We don't know where the limits are, so we have to let the user move far
+ // enough to find them (and the index pulse which might be right next to
+ // one).
+ if ((*goal)(0, 0) > kMaxRange) {
+ LOG(WARNING, "Goal %s above limit, %f > %f\n", name, (*goal)(0, 0),
+ kMaxRange);
+ (*goal)(0, 0) = kMaxRange;
+ }
+ if ((*goal)(0, 0) < -kMaxRange) {
+ LOG(WARNING, "Goal %s below limit, %f < %f\n", name, (*goal)(0, 0),
+ kMaxRange);
+ (*goal)(0, 0) = -kMaxRange;
+ }
+ }
+}
+
+Hood::Hood() {}
+
void Hood::Reset() {
state_ = State::UNINITIALIZED;
profiled_subsystem_.Reset();
}
void Hood::Iterate(const control_loops::HoodGoal *unsafe_goal,
- const ::frc971::PotAndIndexPosition *position,
- double *output,
- ::frc971::control_loops::ProfiledJointStatus *status) {
+ const ::frc971::IndexPosition *position, double *output,
+ ::frc971::control_loops::IndexProfiledJointStatus *status) {
bool disable = output == nullptr;
profiled_subsystem_.Correct(*position);
@@ -57,7 +86,7 @@
// jump.
profiled_subsystem_.ForceGoal(profiled_subsystem_.position());
// Set up the profile to be the zeroing profile.
- profiled_subsystem_.AdjustProfile(0.10, 1);
+ profiled_subsystem_.AdjustProfile(0.30, 1.0);
// We are not ready to start doing anything yet.
disable = true;
@@ -71,33 +100,36 @@
} else if (disable) {
state_ = State::DISABLED_INITIALIZED;
} else {
- // Seek between the two soft limits.
- if (profiled_subsystem_.position() >
- (profiled_subsystem_.range().lower +
- profiled_subsystem_.range().upper) /
- 2.0) {
+ const double kRange = profiled_subsystem_.range().upper_hard -
+ profiled_subsystem_.range().lower_hard;
+ // Seek +- the range of motion.
+ if (profiled_subsystem_.position() > 0) {
// We are above the middle.
- if (profiled_subsystem_.goal(1, 0) > 0) {
- // And moving up. Keep going to the upper soft limit until we
- // arrive.
- profiled_subsystem_.set_unprofiled_goal(
- profiled_subsystem_.range().upper);
+ if (profiled_subsystem_.goal(1, 0) > 0 &&
+ ::std::abs(profiled_subsystem_.position() -
+ profiled_subsystem_.goal(0, 0)) <
+ kStuckZeroingTrackingError) {
+ // And moving up and not stuck. Keep going until we've gone the
+ // full range of motion or get stuck.
+ profiled_subsystem_.set_unprofiled_goal(kRange);
} else {
- // And no longer moving. Go down to the lower soft limit.
- profiled_subsystem_.set_unprofiled_goal(
- profiled_subsystem_.range().lower);
+ // And no longer moving. Go down to the opposite of the range of
+ // motion.
+ profiled_subsystem_.set_unprofiled_goal(-kRange);
}
} else {
// We are below the middle.
- if (profiled_subsystem_.goal(1, 0) < 0) {
- // And moving down. Keep going to the lower soft limit until we
- // arrive.
- profiled_subsystem_.set_unprofiled_goal(
- profiled_subsystem_.range().lower);
+ if (profiled_subsystem_.goal(1, 0) < 0 &&
+ ::std::abs(profiled_subsystem_.position() -
+ profiled_subsystem_.goal(0, 0)) <
+ kStuckZeroingTrackingError) {
+ // And moving down and not stuck. Keep going until we've gone the
+ // full range of motion or get stuck.
+ profiled_subsystem_.set_unprofiled_goal(-kRange);
} else {
- // And no longer moving. Go up to the upper soft limit.
- profiled_subsystem_.set_unprofiled_goal(
- profiled_subsystem_.range().upper);
+ // And no longer moving. Go up to the opposite of the range of
+ // motion.
+ profiled_subsystem_.set_unprofiled_goal(kRange);
}
}
}
diff --git a/y2017/control_loops/superstructure/hood/hood.h b/y2017/control_loops/superstructure/hood/hood.h
index b96d384..c58e005 100644
--- a/y2017/control_loops/superstructure/hood/hood.h
+++ b/y2017/control_loops/superstructure/hood/hood.h
@@ -9,37 +9,51 @@
namespace superstructure {
namespace hood {
+// Profiled subsystem class with significantly relaxed limits while zeroing. We
+// need relaxed limits, because if you start at the top of the range, you need
+// to go to -range, and if you start at the bottom of the range, you need to go
+// to +range. The standard subsystem doesn't support that.
+class IndexPulseProfiledSubsystem
+ : public ::frc971::control_loops::SingleDOFProfiledSubsystem<
+ ::frc971::zeroing::PulseIndexZeroingEstimator> {
+ public:
+ IndexPulseProfiledSubsystem();
+
+ private:
+ void CapGoal(const char *name, Eigen::Matrix<double, 3, 1> *goal) override;
+};
+
class Hood {
public:
- Hood();
- double goal(int row, int col) const {
- return profiled_subsystem_.goal(row, col);
- }
+ Hood();
+ double goal(int row, int col) const {
+ return profiled_subsystem_.goal(row, col);
+ }
- // The zeroing and operating voltages.
- static constexpr double kZeroingVoltage = 2.5;
- static constexpr double kOperatingVoltage = 12.0;
+ // The zeroing and operating voltages.
+ static constexpr double kZeroingVoltage = 2.5;
+ static constexpr double kOperatingVoltage = 12.0;
- void Iterate(const control_loops::HoodGoal *unsafe_goal,
- const ::frc971::PotAndIndexPosition *position, double *output,
- ::frc971::control_loops::ProfiledJointStatus *status);
+ void Iterate(const control_loops::HoodGoal *unsafe_goal,
+ const ::frc971::IndexPosition *position, double *output,
+ ::frc971::control_loops::IndexProfiledJointStatus *status);
- void Reset();
+ void Reset();
- enum class State : int32_t{
- UNINITIALIZED,
- DISABLED_INITIALIZED,
- ZEROING,
- RUNNING,
- ESTOP,
- };
+ enum class State : int32_t {
+ UNINITIALIZED,
+ DISABLED_INITIALIZED,
+ ZEROING,
+ RUNNING,
+ ESTOP,
+ };
- State state() const { return state_; }
+ State state() const { return state_; }
- private:
- State state_;
+ private:
+ State state_;
- ::frc971::control_loops::SingleDOFProfiledSubsystem<> profiled_subsystem_;
+ IndexPulseProfiledSubsystem profiled_subsystem_;
};
} // namespace hood
diff --git a/y2017/control_loops/superstructure/superstructure.q b/y2017/control_loops/superstructure/superstructure.q
index 397a888..b4e6c9b 100644
--- a/y2017/control_loops/superstructure/superstructure.q
+++ b/y2017/control_loops/superstructure/superstructure.q
@@ -128,7 +128,7 @@
// Each subsystems status.
.frc971.control_loops.AbsoluteProfiledJointStatus intake;
.frc971.control_loops.AbsoluteProfiledJointStatus turret;
- .frc971.control_loops.ProfiledJointStatus hood;
+ .frc971.control_loops.IndexProfiledJointStatus hood;
IndexerStatus indexer;
ShooterStatus shooter;
};
@@ -147,7 +147,7 @@
// The sensor readings for the hood. The units and sign are defined the
// same as what's in the Goal message.
- .frc971.PotAndIndexPosition hood;
+ .frc971.IndexPosition hood;
// Shooter wheel angle in radians.
double theta_shooter;
diff --git a/y2017/control_loops/superstructure/superstructure_lib_test.cc b/y2017/control_loops/superstructure/superstructure_lib_test.cc
index d03c48c..3075f03 100644
--- a/y2017/control_loops/superstructure/superstructure_lib_test.cc
+++ b/y2017/control_loops/superstructure/superstructure_lib_test.cc
@@ -132,7 +132,7 @@
SuperstructureSimulation()
: hood_plant_(new HoodPlant(
::y2017::control_loops::superstructure::hood::MakeHoodPlant())),
- hood_pot_encoder_(constants::Values::kHoodEncoderIndexDifference),
+ hood_encoder_(constants::Values::kHoodEncoderIndexDifference),
turret_plant_(new TurretPlant(
::y2017::control_loops::superstructure::turret::MakeTurretPlant())),
@@ -173,7 +173,7 @@
hood_plant_->mutable_X(0, 0) = start_pos;
hood_plant_->mutable_X(1, 0) = 0.0;
- hood_pot_encoder_.Initialize(
+ hood_encoder_.Initialize(
start_pos, kNoiseScalar,
constants::GetValues().hood.zeroing.measured_index_position);
}
@@ -201,7 +201,7 @@
::aos::ScopedMessagePtr<SuperstructureQueue::Position> position =
superstructure_queue_.position.MakeMessage();
- hood_pot_encoder_.GetSensorValues(&position->hood);
+ hood_encoder_.GetSensorValues(&position->hood);
turret_pot_encoder_.GetSensorValues(&position->turret);
intake_pot_encoder_.GetSensorValues(&position->intake);
position->theta_shooter = shooter_plant_->Y(0, 0);
@@ -311,17 +311,33 @@
indexer_plant_->Update();
}
-
- const double angle_hood = hood_plant_->Y(0, 0);
+ double angle_hood = hood_plant_->Y(0, 0);
const double angle_turret = turret_plant_->Y(0, 0);
const double position_intake = intake_plant_->Y(0, 0);
- hood_pot_encoder_.MoveTo(angle_hood);
+ // The hood is special. We don't want to fault when we hit the hard stop.
+ // We want to freeze the hood at the hard stop.
+ if (angle_hood > constants::Values::kHoodRange.upper_hard) {
+ LOG(INFO, "At the hood upper hard stop of %f\n",
+ constants::Values::kHoodRange.upper_hard);
+ angle_hood = constants::Values::kHoodRange.upper_hard;
+ hood_plant_->mutable_Y(0, 0) = angle_hood;
+ hood_plant_->mutable_X(0, 0) = angle_hood;
+ hood_plant_->mutable_X(1, 0) = 0.0;
+ } else if (angle_hood < constants::Values::kHoodRange.lower_hard) {
+ LOG(INFO, "At the hood lower hard stop of %f\n",
+ constants::Values::kHoodRange.lower_hard);
+ angle_hood = constants::Values::kHoodRange.lower_hard;
+ hood_plant_->mutable_Y(0, 0) = angle_hood;
+ hood_plant_->mutable_X(0, 0) = angle_hood;
+ hood_plant_->mutable_X(1, 0) = 0.0;
+ }
+
+ hood_encoder_.MoveTo(angle_hood);
turret_pot_encoder_.MoveTo(angle_turret);
intake_pot_encoder_.MoveTo(position_intake);
- EXPECT_GE(angle_hood, constants::Values::kHoodRange.lower_hard);
- EXPECT_LE(angle_hood, constants::Values::kHoodRange.upper_hard);
+
EXPECT_GE(angle_turret, constants::Values::kTurretRange.lower_hard);
EXPECT_LE(angle_turret, constants::Values::kTurretRange.upper_hard);
EXPECT_GE(position_intake, constants::Values::kIntakeRange.lower_hard);
@@ -330,7 +346,7 @@
private:
::std::unique_ptr<HoodPlant> hood_plant_;
- PositionSensorSimulator hood_pot_encoder_;
+ PositionSensorSimulator hood_encoder_;
::std::unique_ptr<TurretPlant> turret_plant_;
PositionSensorSimulator turret_pot_encoder_;
@@ -539,6 +555,19 @@
// Makes sure that the voltage on a motor is properly pulled back after
// saturation such that we don't get weird or bad (e.g. oscillating) behaviour.
TEST_F(SuperstructureTest, SaturationTest) {
+ // Zero it before we move.
+ {
+ auto goal = superstructure_queue_.goal.MakeMessage();
+ goal->intake.distance = constants::Values::kIntakeRange.upper;
+ goal->turret.angle = constants::Values::kTurretRange.upper;
+ goal->hood.angle = constants::Values::kHoodRange.upper;
+ ASSERT_TRUE(goal.Send());
+ }
+ RunForTime(chrono::seconds(8));
+ VerifyNearGoal();
+
+ // Try a low acceleration move with a high max velocity and verify the
+ // acceleration is capped like expected.
{
auto goal = superstructure_queue_.goal.MakeMessage();
goal->intake.distance = constants::Values::kIntakeRange.lower;
@@ -560,7 +589,9 @@
set_peak_hood_acceleration(1.1);
RunForTime(chrono::seconds(8));
+ VerifyNearGoal();
+ // Now do a high acceleration move with a low velocity limit.
{
auto goal = superstructure_queue_.goal.MakeMessage();
goal->intake.distance = constants::Values::kIntakeRange.upper;
@@ -753,7 +784,7 @@
SimulateSensorReset();
RunForTime(chrono::milliseconds(100));
- EXPECT_EQ(hood::Hood::State::UNINITIALIZED, superstructure_.hood().state());
+ EXPECT_EQ(hood::Hood::State::ZEROING, superstructure_.hood().state());
EXPECT_EQ(turret::Turret::State::UNINITIALIZED,
superstructure_.turret().state());
EXPECT_EQ(intake::Intake::State::UNINITIALIZED,
@@ -814,7 +845,7 @@
EXPECT_EQ(intake::Intake::State::RUNNING,
superstructure_.intake().state());
- superstructure_plant_.set_hood_power_error(1.0);
+ superstructure_plant_.set_hood_power_error(2.0);
RunForTime(chrono::seconds(1), false);
@@ -822,7 +853,7 @@
EXPECT_EQ(turret::Turret::State::RUNNING, superstructure_.turret().state());
EXPECT_EQ(intake::Intake::State::RUNNING, superstructure_.intake().state());
- RunForTime(chrono::seconds(2), true);
+ RunForTime(chrono::seconds(4), true);
VerifyNearGoal();
}