blob: ad8aaeea5112e7c34d5f2a16cab7d05d05439ec9 [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;
Austin Schuh214e9c12016-11-25 17:26:20 -080016namespace chrono = ::std::chrono;
Daniel Petti9cf68c82015-02-14 14:57:17 -080017
Austin Schuhc3b48df2015-02-22 21:31:37 -080018constexpr double kZeroingVoltage = 4.0;
Daniel Petti9cf68c82015-02-14 14:57:17 -080019
20void ClawCappedStateFeedbackLoop::CapU() {
21 mutable_U(0, 0) = ::std::min(mutable_U(0, 0), max_voltage_);
22 mutable_U(0, 0) = ::std::max(mutable_U(0, 0), -max_voltage_);
23}
24
25double ClawCappedStateFeedbackLoop::UnsaturateOutputGoalChange() {
26 // Compute K matrix to compensate for position errors.
27 double Kp = K(0, 0);
28
29 // Compute how much we need to change R in order to achieve the change in U
30 // that was observed.
31 return -(1.0 / Kp) * (U_uncapped() - U())(0, 0);
32}
33
Ben Fredrickson6b5ba792015-01-25 17:14:40 -080034Claw::Claw(control_loops::ClawQueue *claw)
35 : aos::controls::ControlLoop<control_loops::ClawQueue>(claw),
Daniel Petti9cf68c82015-02-14 14:57:17 -080036 last_piston_edge_(Time::Now()),
Adam Snaidere8e09ba2016-03-05 14:40:52 -080037 claw_loop_(new ClawCappedStateFeedbackLoop(
38 ::y2015::control_loops::claw::MakeClawLoop())),
Austin Schuhbd5308a2015-02-28 21:48:44 -080039 claw_estimator_(constants::GetValues().claw.zeroing),
40 profile_(::aos::controls::kLoopFrequency) {}
Daniel Petti9cf68c82015-02-14 14:57:17 -080041
42void Claw::UpdateZeroingState() {
43 if (claw_estimator_.offset_ratio_ready() < 1.0) {
44 state_ = INITIALIZING;
45 } else if (!claw_estimator_.zeroed()) {
46 state_ = ZEROING;
47 } else {
48 state_ = RUNNING;
49 }
50}
51
52void Claw::Correct() {
53 Eigen::Matrix<double, 1, 1> Y;
54 Y << claw_position();
55 claw_loop_->Correct(Y);
56}
57
58void Claw::SetClawOffset(double offset) {
Adam Snaider3cd11c52015-02-16 02:16:09 +000059 LOG(INFO, "Changing claw offset from %f to %f.\n", claw_offset_, offset);
Daniel Petti9cf68c82015-02-14 14:57:17 -080060 const double doffset = offset - claw_offset_;
61
62 // Adjust the height. The derivative should not need to be updated since the
63 // speed is not changing.
64 claw_loop_->mutable_X_hat(0, 0) += doffset;
65
66 // Modify claw zeroing goal.
67 claw_goal_ += doffset;
68 // Update the cached offset value to the actual value.
69 claw_offset_ = offset;
70}
71
72double Claw::estimated_claw_position() const {
73 return current_position_.joint.encoder + claw_estimator_.offset();
74}
75
76double Claw::claw_position() const {
77 return current_position_.joint.encoder + claw_offset_;
78}
79
80constexpr double kClawZeroingVelocity = 0.2;
81
82double Claw::claw_zeroing_velocity() {
83 const auto &values = constants::GetValues();
84
85 // Zeroing will work as following. At startup, record the offset of the claw.
86 // Then, start moving the claw towards where the index pulse should be. We
87 // search around it a little, and if we don't find anything, we estop.
88 // Otherwise, we're done.
89
90 const double target_pos = values.claw.zeroing.measured_index_position;
91 // How far away we need to stay from the ends of the range while zeroing.
92 constexpr double zeroing_limit = 0.1375;
93 // Keep the zeroing range within the bounds of the mechanism.
94 const double zeroing_range =
95 ::std::min(target_pos - values.claw.wrist.lower_limit - zeroing_limit,
96 values.claw.zeroing_range);
97
98 if (claw_zeroing_velocity_ == 0) {
99 if (estimated_claw_position() > target_pos) {
100 claw_zeroing_velocity_ = -kClawZeroingVelocity;
101 } else {
102 claw_zeroing_velocity_ = kClawZeroingVelocity;
103 }
104 } else if (claw_zeroing_velocity_ > 0 &&
105 estimated_claw_position() > target_pos + zeroing_range) {
106 claw_zeroing_velocity_ = -kClawZeroingVelocity;
107 } else if (claw_zeroing_velocity_ < 0 &&
108 estimated_claw_position() < target_pos - zeroing_range) {
109 claw_zeroing_velocity_ = kClawZeroingVelocity;
110 }
111
112 return claw_zeroing_velocity_;
113}
Ben Fredrickson6b5ba792015-01-25 17:14:40 -0800114
Adam Snaider3cd11c52015-02-16 02:16:09 +0000115void Claw::RunIteration(const control_loops::ClawQueue::Goal *unsafe_goal,
116 const control_loops::ClawQueue::Position *position,
117 control_loops::ClawQueue::Output *output,
118 control_loops::ClawQueue::Status *status) {
Daniel Petti9cf68c82015-02-14 14:57:17 -0800119 const auto &values = constants::GetValues();
Ben Fredrickson6b5ba792015-01-25 17:14:40 -0800120
Daniel Petti9cf68c82015-02-14 14:57:17 -0800121 if (WasReset()) {
122 LOG(ERROR, "WPILib reset! Restarting.\n");
123 claw_estimator_.Reset();
124 state_ = UNINITIALIZED;
125 }
126
127 current_position_ = *position;
128
129 // Bool to track if we should turn the motor on or not.
130 bool disable = output == nullptr;
131 double claw_goal_velocity = 0.0;
132
133 claw_estimator_.UpdateEstimate(position->joint);
134
135 if (state_ != UNINITIALIZED) {
136 Correct();
137 }
138
139 switch (state_) {
140 case UNINITIALIZED:
141 LOG(INFO, "Uninitialized.\n");
142 // Startup. Assume that we are at the origin.
143 claw_offset_ = -position->joint.encoder;
144 claw_loop_->mutable_X_hat().setZero();
145 Correct();
146 state_ = INITIALIZING;
147 disable = true;
148 break;
149
150 case INITIALIZING:
151 LOG(INFO, "Waiting for accurate initial position.\n");
152 disable = true;
153 // Update state_ to accurately represent the state of the zeroing
154 // estimator.
155 UpdateZeroingState();
156
157 if (state_ != INITIALIZING) {
158 // Set the goals to where we are now.
159 claw_goal_ = claw_position();
160 }
161 break;
162
163 case ZEROING:
Brian Silverman5fe6c432015-03-21 11:39:53 -0700164 LOG(DEBUG, "Zeroing.\n");
Daniel Petti9cf68c82015-02-14 14:57:17 -0800165
166 // Update state_.
167 UpdateZeroingState();
168 if (claw_estimator_.zeroed()) {
169 LOG(INFO, "Zeroed!\n");
170 SetClawOffset(claw_estimator_.offset());
171 } else if (!disable) {
172 claw_goal_velocity = claw_zeroing_velocity();
Austin Schuh214e9c12016-11-25 17:26:20 -0800173 claw_goal_ += claw_goal_velocity *
174 chrono::duration_cast<chrono::duration<double>>(
175 ::aos::controls::kLoopFrequency).count();
Daniel Petti9cf68c82015-02-14 14:57:17 -0800176 }
Austin Schuhbd5308a2015-02-28 21:48:44 -0800177
178 // Clear the current profile state if we are zeroing.
179 {
180 Eigen::Matrix<double, 2, 1> current;
181 current.setZero();
182 current << claw_goal_, claw_goal_velocity;
183 profile_.MoveCurrentState(current);
184 }
Daniel Petti9cf68c82015-02-14 14:57:17 -0800185 break;
186
187 case RUNNING:
188 LOG(DEBUG, "Running!\n");
189
190 // Update state_.
191 UpdateZeroingState();
192 if (unsafe_goal) {
Austin Schuhbd5308a2015-02-28 21:48:44 -0800193 // Pick a set of sane defaults if none are specified.
194 if (unsafe_goal->max_velocity != 0.0) {
195 profile_.set_maximum_velocity(unsafe_goal->max_velocity);
196 } else {
197 profile_.set_maximum_velocity(2.5);
198 }
199 if (unsafe_goal->max_acceleration != 0.0) {
200 profile_.set_maximum_acceleration(unsafe_goal->max_acceleration);
201 } else {
202 profile_.set_maximum_acceleration(4.0);
203 }
204
205 const double unfiltered_goal = ::std::max(
206 ::std::min(unsafe_goal->angle, values.claw.wrist.upper_limit),
207 values.claw.wrist.lower_limit);
208 ::Eigen::Matrix<double, 2, 1> goal_state =
209 profile_.Update(unfiltered_goal, unsafe_goal->angular_velocity);
210 claw_goal_ = goal_state(0, 0);
211 claw_goal_velocity = goal_state(1, 0);
Daniel Petti9cf68c82015-02-14 14:57:17 -0800212 }
213
214 if (state_ != RUNNING && state_ != ESTOP) {
215 state_ = UNINITIALIZED;
216 }
217 break;
218
219 case ESTOP:
220 LOG(ERROR, "Estop!\n");
221 disable = true;
222 break;
223 }
224
225 // Make sure goal and position do not exceed the hardware limits if we are
226 // RUNNING.
227 if (state_ == RUNNING) {
228 // Limit goal.
229 claw_goal_ = ::std::min(claw_goal_, values.claw.wrist.upper_limit);
230 claw_goal_ = ::std::max(claw_goal_, values.claw.wrist.lower_limit);
231
232 // Check position.
233 if (claw_position() >= values.claw.wrist.upper_hard_limit ||
234 claw_position() <= values.claw.wrist.lower_hard_limit) {
235 LOG(ERROR, "Claw at %f out of bounds [%f, %f].\n", claw_position(),
236 values.claw.wrist.lower_limit, values.claw.wrist.upper_limit);
Daniel Petti9cf68c82015-02-14 14:57:17 -0800237 }
238 }
239
240 // Set the goals.
241 claw_loop_->mutable_R() << claw_goal_, claw_goal_velocity;
242
243 const double max_voltage = (state_ == RUNNING) ? 12.0 : kZeroingVoltage;
244 claw_loop_->set_max_voltage(max_voltage);
245
246 if (state_ == ESTOP) {
247 disable = true;
248 }
249 claw_loop_->Update(disable);
250
251 if (state_ == INITIALIZING || state_ == ZEROING) {
252 if (claw_loop_->U() != claw_loop_->U_uncapped()) {
253 double deltaR = claw_loop_->UnsaturateOutputGoalChange();
254
255 // Move the claw goal by the amount observed.
Adam Snaider3cd11c52015-02-16 02:16:09 +0000256 LOG(WARNING, "Moving claw goal by %f to handle saturation.\n", deltaR);
Daniel Petti9cf68c82015-02-14 14:57:17 -0800257 claw_goal_ += deltaR;
258 }
259 }
260
261 if (output) {
262 output->voltage = claw_loop_->U(0, 0);
Austin Schuh813b9af2015-03-08 18:46:58 -0700263 if (state_ != RUNNING) {
Daniel Petti9cf68c82015-02-14 14:57:17 -0800264 output->intake_voltage = 0.0;
265 output->rollers_closed = false;
Austin Schuh813b9af2015-03-08 18:46:58 -0700266 } else {
267 if (unsafe_goal) {
268 output->intake_voltage = unsafe_goal->intake;
269 output->rollers_closed = unsafe_goal->rollers_closed;
270 } else {
271 output->intake_voltage = 0.0;
272 output->rollers_closed = false;
273 }
Daniel Petti9cf68c82015-02-14 14:57:17 -0800274 }
275 if (output->rollers_closed != last_rollers_closed_) {
276 last_piston_edge_ = Time::Now();
277 }
278 }
279
280 status->zeroed = state_ == RUNNING;
281 status->estopped = state_ == ESTOP;
282 status->state = state_;
Daniel Pettiab274232015-02-16 19:15:34 -0800283 zeroing::PopulateEstimatorState(claw_estimator_, &status->zeroing_state);
Daniel Petti9cf68c82015-02-14 14:57:17 -0800284
285 status->angle = claw_loop_->X_hat(0, 0);
Austin Schuhbd5308a2015-02-28 21:48:44 -0800286 status->angular_velocity = claw_loop_->X_hat(1, 0);
287
Daniel Petti9cf68c82015-02-14 14:57:17 -0800288 if (output) {
289 status->intake = output->intake_voltage;
290 } else {
291 status->intake = 0;
292 }
Austin Schuhc3b48df2015-02-22 21:31:37 -0800293 status->goal_angle = claw_goal_;
Austin Schuhbd5308a2015-02-28 21:48:44 -0800294 status->goal_velocity = claw_goal_velocity;
Daniel Petti9cf68c82015-02-14 14:57:17 -0800295
296 if (output) {
Brian Silvermanc81a13d2015-12-20 18:25:50 -0500297 status->rollers_open = !output->rollers_closed &&
298 ((Time::Now() - last_piston_edge_).ToSeconds() >=
299 values.claw.piston_switch_time);
300 status->rollers_closed = output->rollers_closed &&
301 ((Time::Now() - last_piston_edge_).ToSeconds() >=
302 values.claw.piston_switch_time);
Daniel Petti9cf68c82015-02-14 14:57:17 -0800303 } else {
304 status->rollers_open = false;
305 status->rollers_closed = false;
306 }
307
308 if (output) {
309 last_rollers_closed_ = output->rollers_closed;
310 }
Ben Fredrickson6b5ba792015-01-25 17:14:40 -0800311}
312
313} // namespace control_loops
314} // namespace frc971