blob: e6ab9e7679f3148ff15fe63016714326cf6d0a67 [file] [log] [blame]
Brian Silvermanb691f5e2015-08-02 11:37:55 -07001#include "y2015/control_loops/claw/claw.h"
Ben Fredrickson6b5ba792015-01-25 17:14:40 -08002
Daniel Petti9cf68c82015-02-14 14:57:17 -08003#include <algorithm>
4
Ben Fredrickson6b5ba792015-01-25 17:14:40 -08005#include "aos/common/controls/control_loops.q.h"
6#include "aos/common/logging/logging.h"
7
Brian Silvermanb691f5e2015-08-02 11:37:55 -07008#include "y2015/constants.h"
9#include "y2015/control_loops/claw/claw_motor_plant.h"
Austin Schuhbd5308a2015-02-28 21:48:44 -080010#include "aos/common/util/trapezoid_profile.h"
Ben Fredrickson6b5ba792015-01-25 17:14:40 -080011
12namespace frc971 {
13namespace control_loops {
14
Daniel Petti9cf68c82015-02-14 14:57:17 -080015using ::aos::time::Time;
16
Austin Schuhc3b48df2015-02-22 21:31:37 -080017constexpr double kZeroingVoltage = 4.0;
Daniel Petti9cf68c82015-02-14 14:57:17 -080018
19void ClawCappedStateFeedbackLoop::CapU() {
20 mutable_U(0, 0) = ::std::min(mutable_U(0, 0), max_voltage_);
21 mutable_U(0, 0) = ::std::max(mutable_U(0, 0), -max_voltage_);
22}
23
24double ClawCappedStateFeedbackLoop::UnsaturateOutputGoalChange() {
25 // Compute K matrix to compensate for position errors.
26 double Kp = K(0, 0);
27
28 // Compute how much we need to change R in order to achieve the change in U
29 // that was observed.
30 return -(1.0 / Kp) * (U_uncapped() - U())(0, 0);
31}
32
Ben Fredrickson6b5ba792015-01-25 17:14:40 -080033Claw::Claw(control_loops::ClawQueue *claw)
34 : aos::controls::ControlLoop<control_loops::ClawQueue>(claw),
Daniel Petti9cf68c82015-02-14 14:57:17 -080035 last_piston_edge_(Time::Now()),
Adam Snaidere8e09ba2016-03-05 14:40:52 -080036 claw_loop_(new ClawCappedStateFeedbackLoop(
37 ::y2015::control_loops::claw::MakeClawLoop())),
Austin Schuhbd5308a2015-02-28 21:48:44 -080038 claw_estimator_(constants::GetValues().claw.zeroing),
39 profile_(::aos::controls::kLoopFrequency) {}
Daniel Petti9cf68c82015-02-14 14:57:17 -080040
41void Claw::UpdateZeroingState() {
42 if (claw_estimator_.offset_ratio_ready() < 1.0) {
43 state_ = INITIALIZING;
44 } else if (!claw_estimator_.zeroed()) {
45 state_ = ZEROING;
46 } else {
47 state_ = RUNNING;
48 }
49}
50
51void Claw::Correct() {
52 Eigen::Matrix<double, 1, 1> Y;
53 Y << claw_position();
54 claw_loop_->Correct(Y);
55}
56
57void Claw::SetClawOffset(double offset) {
Adam Snaider3cd11c52015-02-16 02:16:09 +000058 LOG(INFO, "Changing claw offset from %f to %f.\n", claw_offset_, offset);
Daniel Petti9cf68c82015-02-14 14:57:17 -080059 const double doffset = offset - claw_offset_;
60
61 // Adjust the height. The derivative should not need to be updated since the
62 // speed is not changing.
63 claw_loop_->mutable_X_hat(0, 0) += doffset;
64
65 // Modify claw zeroing goal.
66 claw_goal_ += doffset;
67 // Update the cached offset value to the actual value.
68 claw_offset_ = offset;
69}
70
71double Claw::estimated_claw_position() const {
72 return current_position_.joint.encoder + claw_estimator_.offset();
73}
74
75double Claw::claw_position() const {
76 return current_position_.joint.encoder + claw_offset_;
77}
78
79constexpr double kClawZeroingVelocity = 0.2;
80
81double Claw::claw_zeroing_velocity() {
82 const auto &values = constants::GetValues();
83
84 // Zeroing will work as following. At startup, record the offset of the claw.
85 // Then, start moving the claw towards where the index pulse should be. We
86 // search around it a little, and if we don't find anything, we estop.
87 // Otherwise, we're done.
88
89 const double target_pos = values.claw.zeroing.measured_index_position;
90 // How far away we need to stay from the ends of the range while zeroing.
91 constexpr double zeroing_limit = 0.1375;
92 // Keep the zeroing range within the bounds of the mechanism.
93 const double zeroing_range =
94 ::std::min(target_pos - values.claw.wrist.lower_limit - zeroing_limit,
95 values.claw.zeroing_range);
96
97 if (claw_zeroing_velocity_ == 0) {
98 if (estimated_claw_position() > target_pos) {
99 claw_zeroing_velocity_ = -kClawZeroingVelocity;
100 } else {
101 claw_zeroing_velocity_ = kClawZeroingVelocity;
102 }
103 } else if (claw_zeroing_velocity_ > 0 &&
104 estimated_claw_position() > target_pos + zeroing_range) {
105 claw_zeroing_velocity_ = -kClawZeroingVelocity;
106 } else if (claw_zeroing_velocity_ < 0 &&
107 estimated_claw_position() < target_pos - zeroing_range) {
108 claw_zeroing_velocity_ = kClawZeroingVelocity;
109 }
110
111 return claw_zeroing_velocity_;
112}
Ben Fredrickson6b5ba792015-01-25 17:14:40 -0800113
Adam Snaider3cd11c52015-02-16 02:16:09 +0000114void Claw::RunIteration(const control_loops::ClawQueue::Goal *unsafe_goal,
115 const control_loops::ClawQueue::Position *position,
116 control_loops::ClawQueue::Output *output,
117 control_loops::ClawQueue::Status *status) {
Daniel Petti9cf68c82015-02-14 14:57:17 -0800118 const auto &values = constants::GetValues();
Ben Fredrickson6b5ba792015-01-25 17:14:40 -0800119
Daniel Petti9cf68c82015-02-14 14:57:17 -0800120 if (WasReset()) {
121 LOG(ERROR, "WPILib reset! Restarting.\n");
122 claw_estimator_.Reset();
123 state_ = UNINITIALIZED;
124 }
125
126 current_position_ = *position;
127
128 // Bool to track if we should turn the motor on or not.
129 bool disable = output == nullptr;
130 double claw_goal_velocity = 0.0;
131
132 claw_estimator_.UpdateEstimate(position->joint);
133
134 if (state_ != UNINITIALIZED) {
135 Correct();
136 }
137
138 switch (state_) {
139 case UNINITIALIZED:
140 LOG(INFO, "Uninitialized.\n");
141 // Startup. Assume that we are at the origin.
142 claw_offset_ = -position->joint.encoder;
143 claw_loop_->mutable_X_hat().setZero();
144 Correct();
145 state_ = INITIALIZING;
146 disable = true;
147 break;
148
149 case INITIALIZING:
150 LOG(INFO, "Waiting for accurate initial position.\n");
151 disable = true;
152 // Update state_ to accurately represent the state of the zeroing
153 // estimator.
154 UpdateZeroingState();
155
156 if (state_ != INITIALIZING) {
157 // Set the goals to where we are now.
158 claw_goal_ = claw_position();
159 }
160 break;
161
162 case ZEROING:
Brian Silverman5fe6c432015-03-21 11:39:53 -0700163 LOG(DEBUG, "Zeroing.\n");
Daniel Petti9cf68c82015-02-14 14:57:17 -0800164
165 // Update state_.
166 UpdateZeroingState();
167 if (claw_estimator_.zeroed()) {
168 LOG(INFO, "Zeroed!\n");
169 SetClawOffset(claw_estimator_.offset());
170 } else if (!disable) {
171 claw_goal_velocity = claw_zeroing_velocity();
Adam Snaider3cd11c52015-02-16 02:16:09 +0000172 claw_goal_ +=
173 claw_goal_velocity * ::aos::controls::kLoopFrequency.ToSeconds();
Daniel Petti9cf68c82015-02-14 14:57:17 -0800174 }
Austin Schuhbd5308a2015-02-28 21:48:44 -0800175
176 // Clear the current profile state if we are zeroing.
177 {
178 Eigen::Matrix<double, 2, 1> current;
179 current.setZero();
180 current << claw_goal_, claw_goal_velocity;
181 profile_.MoveCurrentState(current);
182 }
Daniel Petti9cf68c82015-02-14 14:57:17 -0800183 break;
184
185 case RUNNING:
186 LOG(DEBUG, "Running!\n");
187
188 // Update state_.
189 UpdateZeroingState();
190 if (unsafe_goal) {
Austin Schuhbd5308a2015-02-28 21:48:44 -0800191 // Pick a set of sane defaults if none are specified.
192 if (unsafe_goal->max_velocity != 0.0) {
193 profile_.set_maximum_velocity(unsafe_goal->max_velocity);
194 } else {
195 profile_.set_maximum_velocity(2.5);
196 }
197 if (unsafe_goal->max_acceleration != 0.0) {
198 profile_.set_maximum_acceleration(unsafe_goal->max_acceleration);
199 } else {
200 profile_.set_maximum_acceleration(4.0);
201 }
202
203 const double unfiltered_goal = ::std::max(
204 ::std::min(unsafe_goal->angle, values.claw.wrist.upper_limit),
205 values.claw.wrist.lower_limit);
206 ::Eigen::Matrix<double, 2, 1> goal_state =
207 profile_.Update(unfiltered_goal, unsafe_goal->angular_velocity);
208 claw_goal_ = goal_state(0, 0);
209 claw_goal_velocity = goal_state(1, 0);
Daniel Petti9cf68c82015-02-14 14:57:17 -0800210 }
211
212 if (state_ != RUNNING && state_ != ESTOP) {
213 state_ = UNINITIALIZED;
214 }
215 break;
216
217 case ESTOP:
218 LOG(ERROR, "Estop!\n");
219 disable = true;
220 break;
221 }
222
223 // Make sure goal and position do not exceed the hardware limits if we are
224 // RUNNING.
225 if (state_ == RUNNING) {
226 // Limit goal.
227 claw_goal_ = ::std::min(claw_goal_, values.claw.wrist.upper_limit);
228 claw_goal_ = ::std::max(claw_goal_, values.claw.wrist.lower_limit);
229
230 // Check position.
231 if (claw_position() >= values.claw.wrist.upper_hard_limit ||
232 claw_position() <= values.claw.wrist.lower_hard_limit) {
233 LOG(ERROR, "Claw at %f out of bounds [%f, %f].\n", claw_position(),
234 values.claw.wrist.lower_limit, values.claw.wrist.upper_limit);
Daniel Petti9cf68c82015-02-14 14:57:17 -0800235 }
236 }
237
238 // Set the goals.
239 claw_loop_->mutable_R() << claw_goal_, claw_goal_velocity;
240
241 const double max_voltage = (state_ == RUNNING) ? 12.0 : kZeroingVoltage;
242 claw_loop_->set_max_voltage(max_voltage);
243
244 if (state_ == ESTOP) {
245 disable = true;
246 }
247 claw_loop_->Update(disable);
248
249 if (state_ == INITIALIZING || state_ == ZEROING) {
250 if (claw_loop_->U() != claw_loop_->U_uncapped()) {
251 double deltaR = claw_loop_->UnsaturateOutputGoalChange();
252
253 // Move the claw goal by the amount observed.
Adam Snaider3cd11c52015-02-16 02:16:09 +0000254 LOG(WARNING, "Moving claw goal by %f to handle saturation.\n", deltaR);
Daniel Petti9cf68c82015-02-14 14:57:17 -0800255 claw_goal_ += deltaR;
256 }
257 }
258
259 if (output) {
260 output->voltage = claw_loop_->U(0, 0);
Austin Schuh813b9af2015-03-08 18:46:58 -0700261 if (state_ != RUNNING) {
Daniel Petti9cf68c82015-02-14 14:57:17 -0800262 output->intake_voltage = 0.0;
263 output->rollers_closed = false;
Austin Schuh813b9af2015-03-08 18:46:58 -0700264 } else {
265 if (unsafe_goal) {
266 output->intake_voltage = unsafe_goal->intake;
267 output->rollers_closed = unsafe_goal->rollers_closed;
268 } else {
269 output->intake_voltage = 0.0;
270 output->rollers_closed = false;
271 }
Daniel Petti9cf68c82015-02-14 14:57:17 -0800272 }
273 if (output->rollers_closed != last_rollers_closed_) {
274 last_piston_edge_ = Time::Now();
275 }
276 }
277
278 status->zeroed = state_ == RUNNING;
279 status->estopped = state_ == ESTOP;
280 status->state = state_;
Daniel Pettiab274232015-02-16 19:15:34 -0800281 zeroing::PopulateEstimatorState(claw_estimator_, &status->zeroing_state);
Daniel Petti9cf68c82015-02-14 14:57:17 -0800282
283 status->angle = claw_loop_->X_hat(0, 0);
Austin Schuhbd5308a2015-02-28 21:48:44 -0800284 status->angular_velocity = claw_loop_->X_hat(1, 0);
285
Daniel Petti9cf68c82015-02-14 14:57:17 -0800286 if (output) {
287 status->intake = output->intake_voltage;
288 } else {
289 status->intake = 0;
290 }
Austin Schuhc3b48df2015-02-22 21:31:37 -0800291 status->goal_angle = claw_goal_;
Austin Schuhbd5308a2015-02-28 21:48:44 -0800292 status->goal_velocity = claw_goal_velocity;
Daniel Petti9cf68c82015-02-14 14:57:17 -0800293
294 if (output) {
Brian Silvermanc81a13d2015-12-20 18:25:50 -0500295 status->rollers_open = !output->rollers_closed &&
296 ((Time::Now() - last_piston_edge_).ToSeconds() >=
297 values.claw.piston_switch_time);
298 status->rollers_closed = output->rollers_closed &&
299 ((Time::Now() - last_piston_edge_).ToSeconds() >=
300 values.claw.piston_switch_time);
Daniel Petti9cf68c82015-02-14 14:57:17 -0800301 } else {
302 status->rollers_open = false;
303 status->rollers_closed = false;
304 }
305
306 if (output) {
307 last_rollers_closed_ = output->rollers_closed;
308 }
Ben Fredrickson6b5ba792015-01-25 17:14:40 -0800309}
310
311} // namespace control_loops
312} // namespace frc971