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