blob: 9444b66529f2dad79e00a90ccae7ee41116226b0 [file] [log] [blame]
Austin Schuh473a5652017-02-05 01:30:42 -08001#ifndef FRC971_CONTROL_LOOPS_PROFILED_SUBSYSTEM_H_
2#define FRC971_CONTROL_LOOPS_PROFILED_SUBSYSTEM_H_
3
4#include <array>
5#include <memory>
6#include <utility>
7
8#include "Eigen/Dense"
9
10#include "aos/common/controls/control_loop.h"
11#include "aos/common/util/trapezoid_profile.h"
12#include "frc971/control_loops/simple_capped_state_feedback_loop.h"
13#include "frc971/control_loops/state_feedback_loop.h"
14#include "frc971/zeroing/zeroing.h"
15
16namespace frc971 {
17namespace control_loops {
18
Brian Silvermanab0b6772017-02-05 16:16:21 -080019// TODO(Brian): Use a tuple instead of an array to support heterogeneous zeroing
20// styles.
21template <int number_of_states, int number_of_axes,
22 class ZeroingEstimator =
23 ::frc971::zeroing::PotAndIndexPulseZeroingEstimator>
Austin Schuh473a5652017-02-05 01:30:42 -080024class ProfiledSubsystem {
25 public:
26 ProfiledSubsystem(
27 ::std::unique_ptr<::frc971::control_loops::SimpleCappedStateFeedbackLoop<
Tyler Chatowf8f03112017-02-05 14:31:34 -080028 number_of_states, number_of_axes, number_of_axes>> loop,
Brian Silvermanab0b6772017-02-05 16:16:21 -080029 ::std::array<ZeroingEstimator, number_of_axes> &&estimators)
Austin Schuh473a5652017-02-05 01:30:42 -080030 : loop_(::std::move(loop)), estimators_(::std::move(estimators)) {
31 zeroed_.fill(false);
32 unprofiled_goal_.setZero();
33 }
34
35 // Returns whether an error has occured
36 bool error() const {
37 for (const auto &estimator : estimators_) {
38 if (estimator.error()) {
39 return true;
40 }
41 }
42 return false;
43 }
44
45 void Reset() {
46 zeroed_.fill(false);
47 for (auto &estimator : estimators_) {
48 estimator.Reset();
49 }
50 }
51
52 // Returns the controller.
Tyler Chatowf8f03112017-02-05 14:31:34 -080053 const StateFeedbackLoop<number_of_states, number_of_axes, number_of_axes> &
54 controller() const {
Austin Schuh473a5652017-02-05 01:30:42 -080055 return *loop_;
56 }
57
58 int controller_index() const { return loop_->controller_index(); }
59
60 // Returns whether the estimators have been initialized and zeroed.
61 bool initialized() const { return initialized_; }
62
63 bool zeroed() const {
64 for (int i = 0; i < number_of_axes; ++i) {
65 if (!zeroed_[i]) {
66 return false;
67 }
68 }
69 return true;
70 }
71
72 bool zeroed(int index) const { return zeroed_[index]; };
73
74 // Returns the filtered goal.
75 const Eigen::Matrix<double, number_of_states, 1> &goal() const {
76 return loop_->R();
77 }
78 double goal(int row, int col) const { return loop_->R(row, col); }
79
80 // Returns the unprofiled goal.
81 const Eigen::Matrix<double, number_of_states, 1> &unprofiled_goal() const {
82 return unprofiled_goal_;
83 }
84 double unprofiled_goal(int row, int col) const {
85 return unprofiled_goal_(row, col);
86 }
87
88 // Returns the current state estimate.
89 const Eigen::Matrix<double, number_of_states, 1> &X_hat() const {
90 return loop_->X_hat();
91 }
92 double X_hat(int row, int col) const { return loop_->X_hat(row, col); }
93
94 // Returns the current internal estimator state for logging.
95 ::frc971::EstimatorState EstimatorState(int index) {
96 ::frc971::EstimatorState estimator_state;
97 ::frc971::zeroing::PopulateEstimatorState(estimators_[index],
98 &estimator_state);
99
100 return estimator_state;
101 }
102
103 // Sets the maximum voltage that will be commanded by the loop.
104 void set_max_voltage(::std::array<double, number_of_axes> voltages) {
105 for (int i = 0; i < number_of_axes; ++i) {
106 loop_->set_max_voltage(i, voltages[i]);
107 }
108 }
109
110 protected:
111 void set_zeroed(int index, bool val) { zeroed_[index] = val; }
112
113 // TODO(austin): It's a bold assumption to assume that we will have the same
114 // number of sensors as axes. So far, that's been fine.
115 ::std::unique_ptr<::frc971::control_loops::SimpleCappedStateFeedbackLoop<
Tyler Chatowf8f03112017-02-05 14:31:34 -0800116 number_of_states, number_of_axes, number_of_axes>> loop_;
Austin Schuh473a5652017-02-05 01:30:42 -0800117
118 // The goal that the profile tries to reach.
119 Eigen::Matrix<double, number_of_states, 1> unprofiled_goal_;
120
121 bool initialized_ = false;
122
Brian Silvermanab0b6772017-02-05 16:16:21 -0800123 ::std::array<ZeroingEstimator, number_of_axes> estimators_;
Austin Schuh473a5652017-02-05 01:30:42 -0800124
125 private:
126 ::std::array<bool, number_of_axes> zeroed_;
127};
128
Brian Silvermanab0b6772017-02-05 16:16:21 -0800129template <class ZeroingEstimator =
130 ::frc971::zeroing::PotAndIndexPulseZeroingEstimator>
Austin Schuh473a5652017-02-05 01:30:42 -0800131class SingleDOFProfiledSubsystem
132 : public ::frc971::control_loops::ProfiledSubsystem<3, 1> {
133 public:
134 SingleDOFProfiledSubsystem(
135 ::std::unique_ptr<SimpleCappedStateFeedbackLoop<3, 1, 1>> loop,
Brian Silvermanab0b6772017-02-05 16:16:21 -0800136 const typename ZeroingEstimator::ZeroingConstants &zeroing_constants,
Austin Schuh473a5652017-02-05 01:30:42 -0800137 const ::frc971::constants::Range &range, double default_angular_velocity,
138 double default_angular_acceleration);
139
140 // Updates our estimator with the latest position.
Brian Silvermanab0b6772017-02-05 16:16:21 -0800141 void Correct(typename ZeroingEstimator::Position position);
Austin Schuh473a5652017-02-05 01:30:42 -0800142 // Runs the controller and profile generator for a cycle.
143 void Update(bool disabled);
144
145 // Forces the current goal to the provided goal, bypassing the profiler.
146 void ForceGoal(double goal);
147 // Sets the unprofiled goal. The profiler will generate a profile to go to
148 // this goal.
149 void set_unprofiled_goal(double unprofiled_goal);
150 // Limits our profiles to a max velocity and acceleration for proper motion.
151 void AdjustProfile(double max_angular_velocity,
152 double max_angular_acceleration);
153
154 // Returns true if we have exceeded any hard limits.
155 bool CheckHardLimits();
156
157 // Returns the requested intake voltage.
158 double intake_voltage() const { return loop_->U(0, 0); }
159
160 // Returns the current position.
161 double angle() const { return Y_(0, 0); }
162
163 // For testing:
164 // Triggers an estimator error.
165 void TriggerEstimatorError() { estimators_[0].TriggerError(); }
166
167 private:
168 // Limits the provided goal to the soft limits. Prints "name" when it fails
169 // to aid debugging.
170 void CapGoal(const char *name, Eigen::Matrix<double, 3, 1> *goal);
171
172 void UpdateOffset(double offset);
173
174 aos::util::TrapezoidProfile profile_;
175
176 // Current measurement.
177 Eigen::Matrix<double, 1, 1> Y_;
178 // Current offset. Y_ = offset_ + raw_sensor;
179 Eigen::Matrix<double, 1, 1> offset_;
180
181 const ::frc971::constants::Range range_;
182
183 const double default_velocity_;
184 const double default_acceleration_;
185};
186
Brian Silvermanab0b6772017-02-05 16:16:21 -0800187namespace internal {
188
189double UseUnlessZero(double target_value, double default_value);
190
191} // namespace internal
192
193template <class ZeroingEstimator>
194SingleDOFProfiledSubsystem<ZeroingEstimator>::SingleDOFProfiledSubsystem(
195 ::std::unique_ptr<SimpleCappedStateFeedbackLoop<3, 1, 1>> loop,
196 const typename ZeroingEstimator::ZeroingConstants &zeroing_constants,
197 const ::frc971::constants::Range &range, double default_velocity,
198 double default_acceleration)
199 : ProfiledSubsystem(::std::move(loop), {{zeroing_constants}}),
200 profile_(::aos::controls::kLoopFrequency),
201 range_(range),
202 default_velocity_(default_velocity),
203 default_acceleration_(default_acceleration) {
204 Y_.setZero();
205 offset_.setZero();
206 AdjustProfile(0.0, 0.0);
207}
208
209template <class ZeroingEstimator>
210void SingleDOFProfiledSubsystem<ZeroingEstimator>::UpdateOffset(double offset) {
211 const double doffset = offset - offset_(0, 0);
212 LOG(INFO, "Adjusting offset from %f to %f\n", offset_(0, 0), offset);
213
214 loop_->mutable_X_hat()(0, 0) += doffset;
215 Y_(0, 0) += doffset;
216 loop_->mutable_R(0, 0) += doffset;
217
218 profile_.MoveGoal(doffset);
219 offset_(0, 0) = offset;
220
221 CapGoal("R", &loop_->mutable_R());
222}
223
224template <class ZeroingEstimator>
225void SingleDOFProfiledSubsystem<ZeroingEstimator>::Correct(
226 typename ZeroingEstimator::Position position) {
227 estimators_[0].UpdateEstimate(position);
228
229 if (estimators_[0].error()) {
230 LOG(ERROR, "zeroing error with intake_estimator\n");
231 return;
232 }
233
234 if (!initialized_) {
235 if (estimators_[0].offset_ready()) {
236 UpdateOffset(estimators_[0].offset());
237 initialized_ = true;
238 }
239 }
240
241 if (!zeroed(0) && estimators_[0].zeroed()) {
242 UpdateOffset(estimators_[0].offset());
243 set_zeroed(0, true);
244 }
245
246 Y_ << position.encoder;
247 Y_ += offset_;
248 loop_->Correct(Y_);
249}
250
251template <class ZeroingEstimator>
252void SingleDOFProfiledSubsystem<ZeroingEstimator>::CapGoal(
253 const char *name, Eigen::Matrix<double, 3, 1> *goal) {
254 // Limit the goal to min/max allowable angles.
255 if ((*goal)(0, 0) > range_.upper) {
256 LOG(WARNING, "Intake goal %s above limit, %f > %f\n", name, (*goal)(0, 0),
257 range_.upper);
258 (*goal)(0, 0) = range_.upper;
259 }
260 if ((*goal)(0, 0) < range_.lower) {
261 LOG(WARNING, "Intake goal %s below limit, %f < %f\n", name, (*goal)(0, 0),
262 range_.lower);
263 (*goal)(0, 0) = range_.lower;
264 }
265}
266
267template <class ZeroingEstimator>
268void SingleDOFProfiledSubsystem<ZeroingEstimator>::ForceGoal(double goal) {
269 set_unprofiled_goal(goal);
270 loop_->mutable_R() = unprofiled_goal_;
271 loop_->mutable_next_R() = loop_->R();
272
273 profile_.MoveCurrentState(loop_->R().block<2, 1>(0, 0));
274}
275
276template <class ZeroingEstimator>
277void SingleDOFProfiledSubsystem<ZeroingEstimator>::set_unprofiled_goal(
278 double unprofiled_goal) {
279 unprofiled_goal_(0, 0) = unprofiled_goal;
280 unprofiled_goal_(1, 0) = 0.0;
281 unprofiled_goal_(2, 0) = 0.0;
282 CapGoal("unprofiled R", &unprofiled_goal_);
283}
284
285template <class ZeroingEstimator>
286void SingleDOFProfiledSubsystem<ZeroingEstimator>::Update(bool disable) {
287 if (!disable) {
288 ::Eigen::Matrix<double, 2, 1> goal_state =
289 profile_.Update(unprofiled_goal_(0, 0), unprofiled_goal_(1, 0));
290
291 loop_->mutable_next_R(0, 0) = goal_state(0, 0);
292 loop_->mutable_next_R(1, 0) = goal_state(1, 0);
293 loop_->mutable_next_R(2, 0) = 0.0;
294 CapGoal("next R", &loop_->mutable_next_R());
295 }
296
297 loop_->Update(disable);
298
299 if (!disable && loop_->U(0, 0) != loop_->U_uncapped(0, 0)) {
300 profile_.MoveCurrentState(loop_->R().block<2, 1>(0, 0));
301 }
302}
303
304template <class ZeroingEstimator>
305bool SingleDOFProfiledSubsystem<ZeroingEstimator>::CheckHardLimits() {
306 // Returns whether hard limits have been exceeded.
307
308 if (angle() > range_.upper_hard || angle() < range_.lower_hard) {
309 LOG(ERROR,
310 "SingleDOFProfiledSubsystem at %f out of bounds [%f, %f], ESTOPing\n",
311 angle(), range_.lower_hard, range_.upper_hard);
312 return true;
313 }
314
315 return false;
316}
317
318template <class ZeroingEstimator>
319void SingleDOFProfiledSubsystem<ZeroingEstimator>::AdjustProfile(
320 double max_angular_velocity, double max_angular_acceleration) {
321 profile_.set_maximum_velocity(
322 internal::UseUnlessZero(max_angular_velocity, default_velocity_));
323 profile_.set_maximum_acceleration(
324 internal::UseUnlessZero(max_angular_acceleration, default_acceleration_));
325}
326
Austin Schuh473a5652017-02-05 01:30:42 -0800327} // namespace control_loops
328} // namespace frc971
329
330#endif // FRC971_CONTROL_LOOPS_PROFILED_SUBSYSTEM_H_