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