1516X Push Back 1.0
1516X's robot code for the 2025-2026 VEX Robotics Competition
Loading...
Searching...
No Matches
Chassis.cpp
Go to the documentation of this file.
1#include "Chassis.h"
2#include <cmath>
3#include <algorithm>
4#include <sys/_intsup.h>
5#include "globals.h"
6#include "pros/abstract_motor.hpp"
7#include "pros/motors.h"
8
9constexpr float INCH_TO_METER = 0.0254f;
10
11Chassis::Chassis(lemlib::Drivetrain drivetrain,
12 lemlib::ControllerSettings lateralSettings,
13 lemlib::ControllerSettings angularSettings,
14 lemlib::OdomSensors sensors,
15 lemlib::DriveCurve* throttleCurve,
16 lemlib::DriveCurve* steerCurve)
17 : lemlib::Chassis(drivetrain, lateralSettings, angularSettings, sensors, throttleCurve, steerCurve),
18 drivetrain(drivetrain) {}
19
20Chassis::WheelPowers Chassis::desaturate(float lateral, float angular, float maxSpeed) {
21 float left = lateral + angular;
22 float right = lateral - angular;
23 float maxMag = std::max(std::abs(left), std::abs(right));
24 if (maxMag > maxSpeed) {
25 left = (left / maxMag) * maxSpeed;
26 right = (right / maxMag) * maxSpeed;
27 }
28 return {left, right};
29}
30
31void Chassis::moveToPointRamsete(float target_x, float target_y, int timeout_msec, const VelocityControllerConfig &config, MoveToPointParams params, bool async) {
32
33 this->requestMotionStart();
34 this->distTraveled = 0;
35
36 auto movement_logic = [=, this]() mutable {
37 target_x *= INCH_TO_METER;
38 target_y *= INCH_TO_METER;
39 float earlyExit_m = params.earlyExitRange * INCH_TO_METER;
40
41 VoltageController controller(
42 config.kV, config.KA_straight, config.KA_turn,
43 config.KS_straight, config.KS_turn,
44 config.KP_straight, config.KI_straight,
45 5000.0, 11.45 * INCH_TO_METER
46 );
47
48 lemlib::PID angularPID(11, 0.001, 0);
49 lemlib::PID lateralPID(5, 0, 0);
50 lemlib::ExitCondition lateral(0.08, 100);
51 lemlib::ExitCondition longitudinal(0.08, 100);
52
53 float lateralGain = 0;
54
55 auto sign = [](float x) { return (x > 0) ? 1.0f : ((x < 0) ? -1.0f : 0.0f); };
56 auto sinc = [](float x) { return (std::abs(x) < 1e-5) ? 1.0f : std::sin(x) / x; };
57
58 float rpm_to_mps_factor = 0.00324173f;
59 bool is_settling = false;
60
61 lemlib::Pose lastPose = this->getPose(true, true);
62 uint32_t startTime = pros::millis();
63 while ((pros::millis() - startTime < static_cast<uint32_t>(timeout_msec)) && (!lateral.getExit() || !longitudinal.getExit())) {
64
65 if (this->motionQueued) {
66 break;
67 }
68
69 lemlib::Pose currentPoseRaw = this->getPose(true, true);
70 this->distTraveled += lastPose.distance(currentPoseRaw);
71 lastPose = currentPoseRaw;
72
73 lemlib::Pose currentPose = currentPoseRaw;
74 currentPose.x *= INCH_TO_METER;
75 currentPose.y *= INCH_TO_METER;
76
77 float d = std::hypot(target_x - currentPose.x, target_y - currentPose.y);
78 float dx = target_x - currentPose.x;
79 float dy = target_y - currentPose.y;
80
81 if (!params.forwards) {
82 dx = -dx;
83 dy = -dy;
84 }
85
86 float theta = currentPose.theta;
87 float localErrorX = std::cos(theta) * dx + std::sin(theta) * dy;
88 float localErrorY = -std::sin(theta) * dx + std::cos(theta) * dy;
89
90 float angularError = std::atan2(localErrorY, localErrorX);
91 float cosineScaling = std::cos(angularError);
92 float driveError;
93
94 if (d < 0.05f) {
95 is_settling = true;
96 }
97
98 if (is_settling) {
99 angularError = 0;
100 cosineScaling = 1.0f;
101 driveError = std::cos(theta) * dx + std::sin(theta) * dy;
102 } else {
103 driveError = d * sign(cosineScaling);
104 }
105
106 if (params.minSpeed > 0 && (d < earlyExit_m || driveError < 0)) {
107 break;
108 }
109
110 lateral.update(localErrorY);
111 longitudinal.update(localErrorX);
112
113 float angularOutput = angularPID.update(angularError);
114 float driveOutput = lateralPID.update(driveError);
115
116 driveOutput = std::clamp(driveOutput, -params.maxSpeed, params.maxSpeed);
117 driveOutput = driveOutput * std::abs(cosineScaling);
118
119 if (is_settling) {
120 angularOutput = 0;
121 } else {
122 angularOutput += driveOutput * lateralGain * localErrorY * sinc(angularError);
123 }
124
125 if (!is_settling && params.minSpeed > 0 && std::abs(driveOutput) < params.minSpeed) {
126 driveOutput = params.minSpeed * sign(driveOutput);
127 if (driveOutput == 0) driveOutput = params.minSpeed;
128 }
129
130 angularOutput = std::clamp(angularOutput, -params.maxTurnSpeed, params.maxTurnSpeed);
131
132 if (!params.forwards) {
133 driveOutput = -driveOutput;
134 }
135
136 float leftVel = drivetrain.leftMotors->get_actual_velocity() * rpm_to_mps_factor;
137 float rightVel = drivetrain.rightMotors->get_actual_velocity() * rpm_to_mps_factor;
138
139 DrivetrainVoltages outputVoltages = controller.update(driveOutput, angularOutput, leftVel, rightVel);
140
141 drivetrain.leftMotors->move_voltage(outputVoltages.leftVoltage * 1000);
142 drivetrain.rightMotors->move_voltage(outputVoltages.rightVoltage * 1000);
143
144 pros::delay(10);
145 }
146
147 if (params.minSpeed == 0) {
148 drivetrain.leftMotors->move_voltage(0);
149 drivetrain.rightMotors->move_voltage(0);
150 }
151
152 this->motionRunning = false;
153 };
154
155 if (async) {
156 pros::Task task(movement_logic);
157 } else {
158 movement_logic();
159 }
160}
161
162void Chassis::moveToPoseRamsete(float targetX, float targetY, float targetTheta, int timeout_msec, const VelocityControllerConfig &config, MoveToPoseParams params, bool async) {
163
164 this->requestMotionStart();
165 this->distTraveled = 0;
166
167 auto movement_logic = [=, this]() mutable {
168 targetX *= INCH_TO_METER;
169 targetY *= INCH_TO_METER;
170 float settleRadius_m = params.settleRadius * INCH_TO_METER;
171 float earlyExit_m = params.earlyExitRange * INCH_TO_METER;
172
173 VoltageController controller(
174 config.kV, config.KA_straight, config.KA_turn,
175 config.KS_straight, config.KS_turn,
176 config.KP_straight, config.KI_straight,
177 5000.0, 11.45 * INCH_TO_METER
178 );
179
180 lemlib::PID angularPID(7.5, 0.001, 26.5);
181 lemlib::PID lateralPID(5.5 , 0.001 , 27);
182
183 lemlib::ExitCondition lateralExit(0.08, 100);
184 lemlib::ExitCondition longitudinalExit(0.08, 100);
185
186 auto sign_func = [](float x) { return (x > 0) ? 1.0f : ((x < 0) ? -1.0f : 0.0f); };
187 auto sinc = [](float x) { return (std::abs(x) < 1e-5) ? 1.0f : std::sin(x) / x; };
188
189 float rpm_to_mps_factor = 0.00324173f;
190
191 float targetThetaRad = targetTheta * (M_PI / 180.0f);
192 float end_unit_x = std::sin(targetThetaRad);
193 float end_unit_y = std::cos(targetThetaRad);
194 float dir_sign = params.forwards ? 1.0f : -1.0f;
195
196 bool is_settling = false;
197
198 lemlib::Pose lastPose = this->getPose(true, true);
199 uint32_t startTime = pros::millis();
200
201 while ((pros::millis() - startTime < static_cast<uint32_t>(timeout_msec)) && (!lateralExit.getExit() || !longitudinalExit.getExit())) {
202
203 if (this->motionQueued) {
204 break;
205 }
206
207 lemlib::Pose currentPoseRaw = this->getPose(true, true);
208 this->distTraveled += lastPose.distance(currentPoseRaw);
209 lastPose = currentPoseRaw;
210
211 lemlib::Pose currentPose = currentPoseRaw;
212 currentPose.x *= INCH_TO_METER;
213 currentPose.y *= INCH_TO_METER;
214 float theta = currentPose.theta;
215
216 float d = std::hypot(targetX - currentPose.x, targetY - currentPose.y);
217
218 float true_dx = targetX - currentPose.x;
219 float true_dy = targetY - currentPose.y;
220 if (!params.forwards) {
221 true_dx = -true_dx;
222 true_dy = -true_dy;
223 }
224 float true_localErrorX = std::cos(theta) * true_dx + std::sin(theta) * true_dy;
225 float true_localErrorY = -std::sin(theta) * true_dx + std::cos(theta) * true_dy;
226
227 float rx = currentPose.x - targetX;
228 float ry = currentPose.y - targetY;
229 float along_track = rx * end_unit_x + ry * end_unit_y;
230 float dynamic_lookahead = d * params.lead;
231 float lookahead_m = std::max(dynamic_lookahead, 0.35f);
232 float ghost_along_track = along_track + (lookahead_m * dir_sign);
233
234 float carrot_x = targetX + ghost_along_track * end_unit_x;
235 float carrot_y = targetY + ghost_along_track * end_unit_y;
236
237 float dx = carrot_x - currentPose.x;
238 float dy = carrot_y - currentPose.y;
239 if (!params.forwards) {
240 dx = -dx;
241 dy = -dy;
242 }
243
244 float localErrorX = std::cos(theta) * dx + std::sin(theta) * dy;
245 float localErrorY = -std::sin(theta) * dx + std::cos(theta) * dy;
246
247 float heading_error_rad = std::atan2(localErrorY, localErrorX);
248 float cosine_scale = std::cos(heading_error_rad);
249
250 float error_norm_sq = localErrorX * localErrorX + localErrorY * localErrorY;
251 float driveError = std::sqrt((error_norm_sq + 2.0f * d * d) / 3.0f) * sign_func(cosine_scale);
252 float turnError = heading_error_rad;
253
254 if (d < std::max(settleRadius_m, 0.05f)) {
255 is_settling = true;
256 }
257
258 if (is_settling) {
259 float targetThetaMath = M_PI_2 - targetThetaRad;
260 float final_error = targetThetaMath - theta;
261 final_error = std::remainder(final_error, 2.0 * M_PI);
262
263 turnError = final_error;
264 driveError = true_localErrorX;
265 cosine_scale = 1.0f;
266 }
267
268 if (params.earlyExitRange > 0 && (d < earlyExit_m || driveError < 0)) {
269 break;
270 }
271
272 lateralExit.update(true_localErrorY);
273 longitudinalExit.update(true_localErrorX);
274
275 float driveOutput = lateralPID.update(driveError);
276 float turnOutput = angularPID.update(turnError);
277
278 driveOutput = std::clamp(driveOutput, -params.maxSpeed, params.maxSpeed);
279
280 if (!is_settling) {
281 float steeringVelocity = std::max(std::abs(driveOutput), 0.2f);
282 turnOutput += steeringVelocity * params.k_lat * localErrorY * sinc(heading_error_rad);
283 }
284
285 driveOutput = std::abs(cosine_scale) * driveOutput;
286
287 if (!is_settling && params.minSpeed > 0 && std::abs(driveOutput) < params.minSpeed) {
288 driveOutput = params.minSpeed * sign_func(driveOutput);
289 if (driveOutput == 0) driveOutput = params.minSpeed;
290 }
291
292 if (!params.forwards) {
293 driveOutput = -driveOutput;
294 }
295
296 turnOutput = std::clamp(turnOutput, -params.maxTurnSpeed, params.maxTurnSpeed);
297
298 float leftVel = drivetrain.leftMotors->get_actual_velocity() * rpm_to_mps_factor;
299 float rightVel = drivetrain.rightMotors->get_actual_velocity() * rpm_to_mps_factor;
300
301 DrivetrainVoltages outputVoltages = controller.update(driveOutput, turnOutput, leftVel, rightVel);
302
303 drivetrain.leftMotors->move_voltage(outputVoltages.leftVoltage * 1000);
304 drivetrain.rightMotors->move_voltage(outputVoltages.rightVoltage * 1000);
305
306 pros::delay(10);
307 }
308
309 if (params.minSpeed == 0) {
310 drivetrain.leftMotors->move_voltage(0);
311 drivetrain.rightMotors->move_voltage(0);
312 }
313
314 this->motionRunning = false;
315 };
316
317 if (async) {
318 pros::Task task(movement_logic);
319 } else {
320 movement_logic();
321 }
322}
323
324void Chassis::tank(float lin_vel, float ang_vel, const VelocityControllerConfig &config, unsigned int time)
325{
326 VoltageController controller(
327 config.kV, config.KA_straight, config.KA_turn,
328 config.KS_straight, config.KS_turn,
329 config.KP_straight, config.KI_straight,
330 5000.0, 11.45 * INCH_TO_METER
331 );
332
333 u_int32_t starting_time = pros::millis();
334
335 while(pros::millis() - starting_time < time)
336 {
337 float leftVel = drivetrain.leftMotors->get_actual_velocity() * 0.00324173f;
338 float rightVel = drivetrain.rightMotors->get_actual_velocity() * 0.00324173f;
339
340 DrivetrainVoltages outputVoltages = controller.update(lin_vel, ang_vel, leftVel, rightVel);
341
342 drivetrain.leftMotors->move_voltage(outputVoltages.leftVoltage * 1000);
343 drivetrain.rightMotors->move_voltage(outputVoltages.rightVoltage * 1000);
344
345 pros::delay(10);
346 }
347
348 drivetrain.leftMotors->brake();
349 drivetrain.rightMotors->brake();
350}
351
352void Chassis::brake(pros::MotorBrake brake_mode)
353{
354 drivetrain.rightMotors->set_brake_mode(brake_mode);
355 drivetrain.leftMotors->set_brake_mode(brake_mode);
356 drivetrain.rightMotors->brake();
357 drivetrain.leftMotors->brake();
358}
359
360void Chassis::tank(int left, int right)
361{
362 drivetrain.rightMotors->move(right);
363 drivetrain.leftMotors->move(left);
364}
365
366bool Chassis::collision_montitoring(unsigned int time)
367{
368 return true;
369}
370
371
372bool Chassis::detect_collision()
373{
374 double right_target = std::abs(drivetrain.rightMotors->get_target_velocity());
375 double left_target = std::abs(drivetrain.leftMotors->get_target_velocity());
376 double right_actual = std::abs(drivetrain.rightMotors->get_actual_velocity());
377 double left_actual = std::abs(drivetrain.leftMotors->get_actual_velocity());
378 double right_eff = drivetrain.rightMotors->get_efficiency();
379 double left_eff = drivetrain.leftMotors->get_efficiency();
380
381 const double TARGET_THRESHOLD = 50.0;
382 const double VELOCITY_THRESHOLD = 5.0;
383 const double EFFICIENCY_THRESHOLD = 20.0;
384 bool right_commanded = right_target > TARGET_THRESHOLD;
385 bool left_commanded = left_target > TARGET_THRESHOLD;
386 bool right_struggling = (right_actual < VELOCITY_THRESHOLD) || (right_eff < EFFICIENCY_THRESHOLD);
387 bool left_struggling = (left_actual < VELOCITY_THRESHOLD) || (left_eff < EFFICIENCY_THRESHOLD);
388
389 bool right_colliding = right_commanded && right_struggling;
390 bool left_colliding = left_commanded && left_struggling;
391 static uint32_t stall_start_time = 0;
392
393 if (right_colliding || left_colliding)
394 {
395 if (stall_start_time == 0)
396 {
397 stall_start_time = pros::millis();
398 }
399 if (pros::millis() - stall_start_time > 250)
400 {
401 return true;
402 }
403 }
404 else
405 {
406 stall_start_time = 0;
407 }
408
409 return false;
410}
const VelocityControllerConfig config
pros::Controller controller(pros::E_CONTROLLER_MASTER)
static constexpr float INCH_TO_METER
static constexpr float rpm_to_mps_factor