1516X Push Back 1.0
1516X's robot code for the 2025-2026 VEX Robotics Competition
Loading...
Searching...
No Matches
ramsete.cpp
Go to the documentation of this file.
1#include "ramsete.h"
2#include "lemlib/util.hpp"
3#include <cmath>
4#include <vector>
5#include <string>
6#include <sstream>
7#include <iostream>
8#include <algorithm>
9
10RamsetePathFollower::Vector2::Vector2(float x, float y) : x(x), y(y) {}
11
12std::string RamsetePathFollower::Vector2::latex() const {
13 std::ostringstream oss;
14 oss << "\\left(" << std::fixed << this->x << "," << std::fixed << this->y << "\\right)";
15 return oss.str();
16}
17
18double RamsetePathFollower::sinc(double x) {
19 if (std::abs(x) < 1e-6) {
20 return 1.0 - (x * x) / 6.0 + (x * x * x * x) / 120.0;
21 }
22 return std::sin(x) / x;
23}
24
25double RamsetePathFollower::angleError(double robotAngle, double targetAngle) {
26 constexpr double TWO_PI = 2.0 * M_PI;
27 double diff = std::fmod(targetAngle - robotAngle, TWO_PI);
28 if (diff < -M_PI) diff += TWO_PI;
29 else if (diff >= M_PI) diff -= TWO_PI;
30 return diff;
31}
32
33RamsetePathFollower::RamsetePathFollower(const VelocityControllerConfig& config, float b_, float zeta_)
34 : controller(
35 config.kV, config.KA_straight, config.KA_turn,
36 config.KS_straight, config.KS_turn,
37 config.KP_straight, config.KI_straight,
38 99999.0, TRACK_WIDTH * INCH_TO_METER
39 ), b(b_), zeta(zeta_) {}
40
41void RamsetePathFollower::followPath(const std::string& path_name, const ramseteConfig& r_config) {
42 if (is_running) {
43 cancel();
44 waitUntilDone();
45 }
46
47 is_running = true;
48 cancel_request = false;
49 distance_traveled = 0.0f;
50
51 TaskParams* params = new TaskParams{this, path_name, r_config};
52
53 task = new pros::Task(task_trampoline, params, "RamseteTask");
54
55 pros::delay(10);
56}
57
58void RamsetePathFollower::task_trampoline(void* params) {
59 TaskParams* p = static_cast<TaskParams*>(params);
60 p->instance->followPathImpl(p->path_name, p->config);
61 delete p;
62}
63
64void RamsetePathFollower::waitUntilDone() {
65 while (is_running) {
66 pros::delay(10);
67 }
68}
69
70void RamsetePathFollower::waitUntil(float dist_inches) {
71 while (is_running && distance_traveled < dist_inches) {
72 pros::delay(10);
73 }
74}
75
76void RamsetePathFollower::cancel() {
77 cancel_request = true;
78}
79
80bool RamsetePathFollower::isRunning() {
81 return is_running;
82}
83
84void RamsetePathFollower::followPathImpl(const std::string& path_name, const ramseteConfig& r_config) {
85 std::vector<State> trajectory;
86
87 if(r_config.path_index >= 0 && (size_t)(r_config.path_index) < precomputed_paths.size()) {
88 if (!precomputed_paths.at(r_config.path_index).empty()) {
89 trajectory = precomputed_paths.at(r_config.path_index);
90 }
91 }
92 if (trajectory.empty()) {
93 trajectory = prepare_trajectory(path_name);
94 }
95 if (trajectory.empty()) {
96 is_running = false;
97 return;
98 }
99
100 if(r_config.test) {
101 chassis.setPose(trajectory[0].x / INCH_TO_METER, trajectory[0].y / INCH_TO_METER, M_PI_2 - trajectory[0].heading, true);
102 } else if(r_config.turnFirst) {
103 double targetH = lemlib::radToDeg(M_PI_2 - trajectory[0].heading);
104 chassis.turnToHeading(r_config.backwards ? targetH + 180 : targetH, 1000);
105 }
106
107 std::vector<std::string> logs;
108 int trajectory_size = trajectory.size();
109
110 const float min_k_sq_vel = 0.5f;
111 const int path_dt_ms = 10;
112
113 const double success_tolerance_inches = 0.5;
114 const int max_settle_time_ms = 1500;
115
116 lemlib::Pose start_pose = chassis.getPose();
117 uint32_t global_start_time = pros::millis();
118
119 bool is_settling = false;
120 uint32_t settle_start_time = 0;
121
122 while (!cancel_request) {
123 uint32_t now = pros::millis();
124
125 float t_elapsed_sec = (now - global_start_time) / 1000.0f;
126 float exact_index = t_elapsed_sec / (path_dt_ms / 1000.0f);
127 int idx = static_cast<int>(exact_index);
128
129 State target_state;
130 float current_b = r_config.b;
131
132 if (idx < trajectory_size - 1) {
133 float alpha = exact_index - idx;
134 const State& s0 = trajectory[idx];
135 const State& s1 = trajectory[idx+1];
136
137 target_state.x = s0.x + alpha * (s1.x - s0.x);
138 target_state.y = s0.y + alpha * (s1.y - s0.y);
139 target_state.linear_vel = s0.linear_vel + alpha * (s1.linear_vel - s0.linear_vel);
140 target_state.angular_vel = s0.angular_vel + alpha * (s1.angular_vel - s0.angular_vel);
141
142 double dh = s1.heading - s0.heading;
143 while (dh > M_PI) dh -= 2 * M_PI;
144 while (dh < -M_PI) dh += 2 * M_PI;
145 target_state.heading = s0.heading + alpha * dh;
146 }
147 else {
148 if (!is_settling) {
149 is_settling = true;
150 settle_start_time = now;
151 }
152
153 lemlib::Pose p = chassis.getPose();
154 double p_x_m = p.x * INCH_TO_METER;
155 double p_y_m = p.y * INCH_TO_METER;
156
157 double dist_to_end_m = std::hypot(
158 trajectory.back().x - p_x_m,
159 trajectory.back().y - p_y_m
160 );
161
162 double dist_to_end_in = dist_to_end_m / INCH_TO_METER;
163
164 if (dist_to_end_in < success_tolerance_inches) {
165 break;
166 }
167 if (now - settle_start_time > max_settle_time_ms) {
168 break;
169 }
170
171 target_state = trajectory.back();
172 target_state.linear_vel = 0;
173 target_state.angular_vel = 0;
174
175 current_b = r_config.b * 4.0;
176 }
177
178 lemlib::Pose current_pose = chassis.getPose(true);
179 distance_traveled = start_pose.distance(current_pose);
180
181 current_pose.x *= INCH_TO_METER;
182 current_pose.y *= INCH_TO_METER;
183 current_pose.theta = M_PI_2 - current_pose.theta;
184
185 double targetHeadingAdjusted = target_state.heading + (r_config.backwards ? M_PI : 0);
186 double errorTheta = angleError(current_pose.theta, targetHeadingAdjusted);
187
188 Eigen::Vector3d global_error;
189 global_error << target_state.x - current_pose.x, target_state.y - current_pose.y, errorTheta;
190
191 Eigen::Matrix3d rotation_matrix;
192 rotation_matrix << std::cos(current_pose.theta), std::sin(current_pose.theta), 0,
193 -std::sin(current_pose.theta), std::cos(current_pose.theta), 0,
194 0, 0, 1;
195
196 Eigen::Vector3d local_error = rotation_matrix * global_error;
197 float e_x = local_error(0);
198 float e_y = local_error(1);
199 float e_t = local_error(2);
200
201 float vd = target_state.linear_vel * (r_config.backwards ? -1.0 : 1.0);
202 float wd = target_state.angular_vel;
203
204 float k = 2.0 * r_config.zeta * std::sqrt(wd * wd + current_b * std::max((float)(vd * vd), min_k_sq_vel));
205
206 float v_desired_ramsete = vd * std::cos(e_t) + k * e_x;
207 float w_desired_ramsete = wd + k * e_t + (current_b * vd * sinc(e_t) * e_y);
208
209 float left_actual_mps = leftMotors.get_actual_velocity() * rpm_to_mps_factor;
210 float right_actual_mps = rightMotors.get_actual_velocity() * rpm_to_mps_factor;
211
212 DrivetrainVoltages output_voltages = controller.update(
213 v_desired_ramsete,
214 w_desired_ramsete,
215 left_actual_mps,
216 right_actual_mps
217 );
218
219 rightMotors.move_voltage(output_voltages.rightVoltage * 1000.0);
220 leftMotors.move_voltage(output_voltages.leftVoltage * 1000.0);
221
222 if(r_config.log) {
223 std::ostringstream ss;
224 ss << Vector2(current_pose.x, current_pose.y).latex() << ",";
225 logs.push_back(ss.str());
226 }
227
228 pros::delay(10);
229 }
230
231 rightMotors.brake();
232 leftMotors.brake();
233
234 if(!r_config.test && r_config.end_correction && !cancel_request) {
235 chassis.moveToPose(
236 trajectory.back().x / INCH_TO_METER,
237 trajectory.back().y / INCH_TO_METER,
238 lemlib::radToDeg(M_PI_2 - trajectory.back().heading),
239 1000,
240 {.lead = r_config.mpose_lead}
241 );
242 chassis.waitUntilDone();
243 }
244
245 if(r_config.log) {
246 for (const auto& line : logs) {
247 std::cout << line;
248 pros::delay(50);
249 }
250 }
251
252 is_running = false;
253}
254
255void RamsetePathFollower::precompute_paths(const std::vector<std::string>& path_names) {
256 auto* stored = new std::vector<std::string>(path_names);
257 pros::Task t(precompute_paths_task, stored);
258}
259
260void RamsetePathFollower::precompute_paths_task(void* param) {
261 auto* path_names = static_cast<std::vector<std::string>*>(param);
262
263 precomputed_paths.clear();
264 precomputed_paths.reserve(path_names->size());
265
266 for (const auto& name : *path_names) {
267 precomputed_paths.push_back(prepare_trajectory(name));
268 pros::delay(10);
269 }
270 delete path_names;
271}
272
273std::vector<std::pair<double,double>> RamsetePathFollower::parse_pairs(const std::string& line) {
274 std::vector<std::pair<double,double>> result;
275 std::string temp;
276 bool inside_parens = false;
277 for (char c : line) {
278 if (c == '(') {
279 temp.clear();
280 inside_parens = true;
281 } else if (c == ')') {
282 std::replace(temp.begin(), temp.end(), ',', ' ');
283 std::istringstream ss(temp);
284 double first, second;
285 ss >> first >> second;
286 result.emplace_back(first, second);
287 inside_parens = false;
288 } else if (inside_parens) {
289 temp += c;
290 }
291 }
292 return result;
293}
294
295std::vector<State> RamsetePathFollower::prepare_trajectory(const std::string& data) {
296 std::istringstream ss(data);
297 std::vector<std::pair<double,double>> X, L, A;
298 std::string line;
299 X.reserve(1000); L.reserve(1000); A.reserve(1000);
300
301 while (std::getline(ss, line)) {
302 if (line.find("X =") != std::string::npos) X = parse_pairs(line.substr(line.find('[')));
303 else if (line.find("L =") != std::string::npos) L = parse_pairs(line.substr(line.find('[')));
304 else if (line.find("A =") != std::string::npos) A = parse_pairs(line.substr(line.find('[')));
305 }
306
307 size_t n = X.size();
308 if (n == 0) return {};
309
310 std::vector<State> states(n);
311
312 for (size_t i = 0; i < n; i++) {
313 states[i].x = X[i].first;
314 states[i].y = X[i].second;
315 states[i].linear_vel = L[i].second;
316 states[i].angular_vel = A[i].second;
317 }
318
319 if (n > 1) {
320 states[0].heading = atan2(states[1].y - states[0].y, states[1].x - states[0].x);
321
322 for (size_t i = 1; i < n - 1; i++) {
323 double dy = states[i + 1].y - states[i - 1].y;
324 double dx = states[i + 1].x - states[i - 1].x;
325 states[i].heading = atan2(dy, dx);
326 }
327
328 states[n - 1].heading = atan2(states[n - 1].y - states[n - 2].y, states[n - 1].x - states[n - 2].x);
329 }
330
331 for(size_t i = 1; i < n; ++i) {
332 double diff = states[i].heading - states[i-1].heading;
333 while (diff > M_PI) diff -= 2 * M_PI;
334 while (diff < -M_PI) diff += 2 * M_PI;
335 states[i].heading = states[i-1].heading + diff;
336 }
337
338 return states;
339}
const VelocityControllerConfig config
pros::MotorGroup rightMotors
lemlib::Chassis chassis
pros::MotorGroup leftMotors
Definition globals.cpp:20
pros::Controller controller
double X
Definition MCL.cpp:10
static constexpr float INCH_TO_METER
static constexpr float rpm_to_mps_factor