blob: c1a734e63c9b059051f040a268dedfa8f6d66ffe [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()),
36 claw_loop_(new ClawCappedStateFeedbackLoop(MakeClawLoop())),
Austin Schuhbd5308a2015-02-28 21:48:44 -080037 claw_estimator_(constants::GetValues().claw.zeroing),
38 profile_(::aos::controls::kLoopFrequency) {}
Daniel Petti9cf68c82015-02-14 14:57:17 -080039
40void Claw::UpdateZeroingState() {
41 if (claw_estimator_.offset_ratio_ready() < 1.0) {
42 state_ = INITIALIZING;
43 } else if (!claw_estimator_.zeroed()) {
44 state_ = ZEROING;
45 } else {
46 state_ = RUNNING;
47 }
48}
49
50void Claw::Correct() {
51 Eigen::Matrix<double, 1, 1> Y;
52 Y << claw_position();
53 claw_loop_->Correct(Y);
54}
55
56void Claw::SetClawOffset(double offset) {
Adam Snaider3cd11c52015-02-16 02:16:09 +000057 LOG(INFO, "Changing claw offset from %f to %f.\n", claw_offset_, offset);
Daniel Petti9cf68c82015-02-14 14:57:17 -080058 const double doffset = offset - claw_offset_;
59
60 // Adjust the height. The derivative should not need to be updated since the
61 // speed is not changing.
62 claw_loop_->mutable_X_hat(0, 0) += doffset;
63
64 // Modify claw zeroing goal.
65 claw_goal_ += doffset;
66 // Update the cached offset value to the actual value.
67 claw_offset_ = offset;
68}
69
70double Claw::estimated_claw_position() const {
71 return current_position_.joint.encoder + claw_estimator_.offset();
72}
73
74double Claw::claw_position() const {
75 return current_position_.joint.encoder + claw_offset_;
76}
77
78constexpr double kClawZeroingVelocity = 0.2;
79
80double Claw::claw_zeroing_velocity() {
81 const auto &values = constants::GetValues();
82
83 // Zeroing will work as following. At startup, record the offset of the claw.
84 // Then, start moving the claw towards where the index pulse should be. We
85 // search around it a little, and if we don't find anything, we estop.
86 // Otherwise, we're done.
87
88 const double target_pos = values.claw.zeroing.measured_index_position;
89 // How far away we need to stay from the ends of the range while zeroing.
90 constexpr double zeroing_limit = 0.1375;
91 // Keep the zeroing range within the bounds of the mechanism.
92 const double zeroing_range =
93 ::std::min(target_pos - values.claw.wrist.lower_limit - zeroing_limit,
94 values.claw.zeroing_range);
95
96 if (claw_zeroing_velocity_ == 0) {
97 if (estimated_claw_position() > target_pos) {
98 claw_zeroing_velocity_ = -kClawZeroingVelocity;
99 } else {
100 claw_zeroing_velocity_ = kClawZeroingVelocity;
101 }
102 } else if (claw_zeroing_velocity_ > 0 &&
103 estimated_claw_position() > target_pos + zeroing_range) {
104 claw_zeroing_velocity_ = -kClawZeroingVelocity;
105 } else if (claw_zeroing_velocity_ < 0 &&
106 estimated_claw_position() < target_pos - zeroing_range) {
107 claw_zeroing_velocity_ = kClawZeroingVelocity;
108 }
109
110 return claw_zeroing_velocity_;
111}
Ben Fredrickson6b5ba792015-01-25 17:14:40 -0800112
Adam Snaider3cd11c52015-02-16 02:16:09 +0000113void Claw::RunIteration(const control_loops::ClawQueue::Goal *unsafe_goal,
114 const control_loops::ClawQueue::Position *position,
115 control_loops::ClawQueue::Output *output,
116 control_loops::ClawQueue::Status *status) {
Daniel Petti9cf68c82015-02-14 14:57:17 -0800117 const auto &values = constants::GetValues();
Ben Fredrickson6b5ba792015-01-25 17:14:40 -0800118
Daniel Petti9cf68c82015-02-14 14:57:17 -0800119 if (WasReset()) {
120 LOG(ERROR, "WPILib reset! Restarting.\n");
121 claw_estimator_.Reset();
122 state_ = UNINITIALIZED;
123 }
124
125 current_position_ = *position;
126
127 // Bool to track if we should turn the motor on or not.
128 bool disable = output == nullptr;
129 double claw_goal_velocity = 0.0;
130
131 claw_estimator_.UpdateEstimate(position->joint);
132
133 if (state_ != UNINITIALIZED) {
134 Correct();
135 }
136
137 switch (state_) {
138 case UNINITIALIZED:
139 LOG(INFO, "Uninitialized.\n");
140 // Startup. Assume that we are at the origin.
141 claw_offset_ = -position->joint.encoder;
142 claw_loop_->mutable_X_hat().setZero();
143 Correct();
144 state_ = INITIALIZING;
145 disable = true;
146 break;
147
148 case INITIALIZING:
149 LOG(INFO, "Waiting for accurate initial position.\n");
150 disable = true;
151 // Update state_ to accurately represent the state of the zeroing
152 // estimator.
153 UpdateZeroingState();
154
155 if (state_ != INITIALIZING) {
156 // Set the goals to where we are now.
157 claw_goal_ = claw_position();
158 }
159 break;
160
161 case ZEROING:
Brian Silverman5fe6c432015-03-21 11:39:53 -0700162 LOG(DEBUG, "Zeroing.\n");
Daniel Petti9cf68c82015-02-14 14:57:17 -0800163
164 // Update state_.
165 UpdateZeroingState();
166 if (claw_estimator_.zeroed()) {
167 LOG(INFO, "Zeroed!\n");
168 SetClawOffset(claw_estimator_.offset());
169 } else if (!disable) {
170 claw_goal_velocity = claw_zeroing_velocity();
Adam Snaider3cd11c52015-02-16 02:16:09 +0000171 claw_goal_ +=
172 claw_goal_velocity * ::aos::controls::kLoopFrequency.ToSeconds();
Daniel Petti9cf68c82015-02-14 14:57:17 -0800173 }
Austin Schuhbd5308a2015-02-28 21:48:44 -0800174
175 // Clear the current profile state if we are zeroing.
176 {
177 Eigen::Matrix<double, 2, 1> current;
178 current.setZero();
179 current << claw_goal_, claw_goal_velocity;
180 profile_.MoveCurrentState(current);
181 }
Daniel Petti9cf68c82015-02-14 14:57:17 -0800182 break;
183
184 case RUNNING:
185 LOG(DEBUG, "Running!\n");
186
187 // Update state_.
188 UpdateZeroingState();
189 if (unsafe_goal) {
Austin Schuhbd5308a2015-02-28 21:48:44 -0800190 // Pick a set of sane defaults if none are specified.
191 if (unsafe_goal->max_velocity != 0.0) {
192 profile_.set_maximum_velocity(unsafe_goal->max_velocity);
193 } else {
194 profile_.set_maximum_velocity(2.5);
195 }
196 if (unsafe_goal->max_acceleration != 0.0) {
197 profile_.set_maximum_acceleration(unsafe_goal->max_acceleration);
198 } else {
199 profile_.set_maximum_acceleration(4.0);
200 }
201
202 const double unfiltered_goal = ::std::max(
203 ::std::min(unsafe_goal->angle, values.claw.wrist.upper_limit),
204 values.claw.wrist.lower_limit);
205 ::Eigen::Matrix<double, 2, 1> goal_state =
206 profile_.Update(unfiltered_goal, unsafe_goal->angular_velocity);
207 claw_goal_ = goal_state(0, 0);
208 claw_goal_velocity = goal_state(1, 0);
Daniel Petti9cf68c82015-02-14 14:57:17 -0800209 }
210
211 if (state_ != RUNNING && state_ != ESTOP) {
212 state_ = UNINITIALIZED;
213 }
214 break;
215
216 case ESTOP:
217 LOG(ERROR, "Estop!\n");
218 disable = true;
219 break;
220 }
221
222 // Make sure goal and position do not exceed the hardware limits if we are
223 // RUNNING.
224 if (state_ == RUNNING) {
225 // Limit goal.
226 claw_goal_ = ::std::min(claw_goal_, values.claw.wrist.upper_limit);
227 claw_goal_ = ::std::max(claw_goal_, values.claw.wrist.lower_limit);
228
229 // Check position.
230 if (claw_position() >= values.claw.wrist.upper_hard_limit ||
231 claw_position() <= values.claw.wrist.lower_hard_limit) {
232 LOG(ERROR, "Claw at %f out of bounds [%f, %f].\n", claw_position(),
233 values.claw.wrist.lower_limit, values.claw.wrist.upper_limit);
Daniel Petti9cf68c82015-02-14 14:57:17 -0800234 }
235 }
236
237 // Set the goals.
238 claw_loop_->mutable_R() << claw_goal_, claw_goal_velocity;
239
240 const double max_voltage = (state_ == RUNNING) ? 12.0 : kZeroingVoltage;
241 claw_loop_->set_max_voltage(max_voltage);
242
243 if (state_ == ESTOP) {
244 disable = true;
245 }
246 claw_loop_->Update(disable);
247
248 if (state_ == INITIALIZING || state_ == ZEROING) {
249 if (claw_loop_->U() != claw_loop_->U_uncapped()) {
250 double deltaR = claw_loop_->UnsaturateOutputGoalChange();
251
252 // Move the claw goal by the amount observed.
Adam Snaider3cd11c52015-02-16 02:16:09 +0000253 LOG(WARNING, "Moving claw goal by %f to handle saturation.\n", deltaR);
Daniel Petti9cf68c82015-02-14 14:57:17 -0800254 claw_goal_ += deltaR;
255 }
256 }
257
258 if (output) {
259 output->voltage = claw_loop_->U(0, 0);
Austin Schuh813b9af2015-03-08 18:46:58 -0700260 if (state_ != RUNNING) {
Daniel Petti9cf68c82015-02-14 14:57:17 -0800261 output->intake_voltage = 0.0;
262 output->rollers_closed = false;
Austin Schuh813b9af2015-03-08 18:46:58 -0700263 } else {
264 if (unsafe_goal) {
265 output->intake_voltage = unsafe_goal->intake;
266 output->rollers_closed = unsafe_goal->rollers_closed;
267 } else {
268 output->intake_voltage = 0.0;
269 output->rollers_closed = false;
270 }
Daniel Petti9cf68c82015-02-14 14:57:17 -0800271 }
272 if (output->rollers_closed != last_rollers_closed_) {
273 last_piston_edge_ = Time::Now();
274 }
275 }
276
277 status->zeroed = state_ == RUNNING;
278 status->estopped = state_ == ESTOP;
279 status->state = state_;
Daniel Pettiab274232015-02-16 19:15:34 -0800280 zeroing::PopulateEstimatorState(claw_estimator_, &status->zeroing_state);
Daniel Petti9cf68c82015-02-14 14:57:17 -0800281
282 status->angle = claw_loop_->X_hat(0, 0);
Austin Schuhbd5308a2015-02-28 21:48:44 -0800283 status->angular_velocity = claw_loop_->X_hat(1, 0);
284
Daniel Petti9cf68c82015-02-14 14:57:17 -0800285 if (output) {
286 status->intake = output->intake_voltage;
287 } else {
288 status->intake = 0;
289 }
Austin Schuhc3b48df2015-02-22 21:31:37 -0800290 status->goal_angle = claw_goal_;
Austin Schuhbd5308a2015-02-28 21:48:44 -0800291 status->goal_velocity = claw_goal_velocity;
Daniel Petti9cf68c82015-02-14 14:57:17 -0800292
293 if (output) {
Adam Snaider3cd11c52015-02-16 02:16:09 +0000294 status->rollers_open =
295 !output->rollers_closed &&
Daniel Petti9cf68c82015-02-14 14:57:17 -0800296 (Time::Now() - last_piston_edge_ >= values.claw.piston_switch_time);
Adam Snaider3cd11c52015-02-16 02:16:09 +0000297 status->rollers_closed =
298 output->rollers_closed &&
Daniel Petti9cf68c82015-02-14 14:57:17 -0800299 (Time::Now() - last_piston_edge_ >= values.claw.piston_switch_time);
300 } else {
301 status->rollers_open = false;
302 status->rollers_closed = false;
303 }
304
305 if (output) {
306 last_rollers_closed_ = output->rollers_closed;
307 }
Ben Fredrickson6b5ba792015-01-25 17:14:40 -0800308}
309
310} // namespace control_loops
311} // namespace frc971