1516X Push Back 1.0
1516X's robot code for the 2025-2026 VEX Robotics Competition
Loading...
Searching...
No Matches
ltv.cpp
Go to the documentation of this file.
1#include "ltv.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
10LTVPathFollower::Vector2::Vector2(float x, float y) : x(x), y(y) {}
11
12std::string LTVPathFollower::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 LTVPathFollower::angleError(double robotAngle, double targetAngle) {
19 constexpr double TWO_PI = 2.0 * M_PI;
20 double diff = std::fmod(targetAngle - robotAngle, TWO_PI);
21 if (diff < -M_PI) diff += TWO_PI;
22 else if (diff >= M_PI) diff -= TWO_PI;
23 return diff;
24}
25
26LTVPathFollower::LTVPathFollower(const VelocityControllerConfig& config)
27 : controller(
28 config.kV,
29 config.KA_straight,
30 config.KA_turn,
31 config.KS_straight,
32 config.KS_turn,
33 config.KP_straight,
34 config.KI_straight,
35 99999.0,
36 12.8f * INCH_TO_METER
37 ) {}
38
39Eigen::MatrixXf LTVPathFollower::dareSolver(const Eigen::MatrixXf &A, const Eigen::MatrixXf &B, const Eigen::MatrixXf &Q, const Eigen::MatrixXf &R) {
40 Eigen::MatrixXf X = Q;
41 Eigen::MatrixXf X_prev;
42 Eigen::MatrixXf K;
43
44 for (int i = 0; i < 1000; ++i) {
45 X_prev = X;
46 K = (R + B.transpose() * X * B).inverse() * B.transpose() * X * A;
47 X = A.transpose() * X * (A - B * K) + Q;
48
49 if ((X - X_prev).norm() < 1e-4) {
50 break;
51 }
52 }
53 return X;
54}
55
56std::pair<Eigen::MatrixXf, Eigen::MatrixXf> LTVPathFollower::discretizeAB(
57 const Eigen::MatrixXf& contA, const Eigen::MatrixXf& contB, double dtSeconds) {
58
59 int states = contA.rows();
60 int inputs = contB.cols();
61
62 Eigen::MatrixXf M(states + inputs, states + inputs);
63 M.setZero();
64 M.topLeftCorner(states, states) = contA;
65 M.topRightCorner(states, inputs) = contB;
66
67 Eigen::MatrixXf Mdt = M * dtSeconds;
68 Eigen::MatrixXf I = Eigen::MatrixXf::Identity(M.rows(), M.cols());
69 Eigen::MatrixXf M2 = Mdt * Mdt;
70
71 Eigen::MatrixXf phi = I + Mdt + (M2 / 2.0);
72
73 Eigen::MatrixXf discA = phi.topLeftCorner(states, states);
74 Eigen::MatrixXf discB = phi.topRightCorner(states, inputs);
75
76 return {discA, discB};
77}
78
79void LTVPathFollower::precompute_paths(const std::vector<std::string>& path_names) {
80 auto* stored = new std::vector<std::string>(path_names);
81 pros::Task t(precompute_paths_task, stored);
82}
83
84void LTVPathFollower::precompute_paths_task(void* param) {
85 auto* path_names = static_cast<std::vector<std::string>*>(param);
86 precomputed_paths.clear();
87 precomputed_paths.reserve(path_names->size());
88 for (const auto& name : *path_names) {
89 precomputed_paths.push_back(prepare_trajectory(name));
90 pros::delay(10);
91 }
92 delete path_names;
93}
94
95std::vector<std::pair<double,double>> LTVPathFollower::parse_pairs(const std::string& line) {
96 std::vector<std::pair<double,double>> result;
97 std::string temp;
98 bool inside_parens = false;
99 for (char c : line) {
100 if (c == '(') {
101 temp.clear();
102 inside_parens = true;
103 } else if (c == ')') {
104 std::replace(temp.begin(), temp.end(), ',', ' ');
105 std::istringstream ss(temp);
106 double first, second;
107 ss >> first >> second;
108 result.emplace_back(first, second);
109 inside_parens = false;
110 } else if (inside_parens) {
111 temp += c;
112 }
113 }
114 return result;
115}
116
117std::vector<State> LTVPathFollower::prepare_trajectory(const std::string& data) {
118 std::istringstream ss(data);
119 std::vector<std::pair<double,double>> X, L, A;
120 std::string line;
121
122 while (std::getline(ss, line)) {
123 if (line.find("X =") != std::string::npos) X = parse_pairs(line.substr(line.find('[')));
124 else if (line.find("L =") != std::string::npos) L = parse_pairs(line.substr(line.find('[')));
125 else if (line.find("A =") != std::string::npos) A = parse_pairs(line.substr(line.find('[')));
126 }
127
128 size_t n = X.size();
129 if (n == 0) return {};
130 std::vector<State> states(n);
131
132 for (size_t i = 0; i < n; i++) {
133 states[i].x = X[i].first;
134 states[i].y = X[i].second;
135 states[i].linear_vel = L[i].second;
136 states[i].angular_vel = A[i].second;
137 }
138
139 if (n > 1) states[0].heading = atan2(states[1].y - states[0].y, states[1].x - states[0].x);
140 for (size_t i = 1; i < n - 1; i++) {
141 states[i].heading = atan2(states[i + 1].y - states[i - 1].y, states[i + 1].x - states[i - 1].x);
142 }
143 if (n > 1) states[n - 1].heading = atan2(states[n - 1].y - states[n - 2].y, states[n - 1].x - states[n - 2].x);
144
145 return states;
146}
147
148void LTVPathFollower::followPath(const std::string& path_name, const ltvConfig& l_config) {
149 std::vector<State> trajectory;
150
151 if(l_config.path_index >= 0 && (size_t)(l_config.path_index) < precomputed_paths.size()) {
152 if (!precomputed_paths.at(l_config.path_index).empty()) {
153 trajectory = precomputed_paths.at(l_config.path_index);
154 }
155 }
156 if (trajectory.empty()) trajectory = prepare_trajectory(path_name);
157 if (trajectory.empty()) return;
158
159 Eigen::Matrix3f Q_mat;
160 Q_mat << l_config.q_x, 0, 0,
161 0, l_config.q_y, 0,
162 0, 0, l_config.q_theta;
163
164 Eigen::Matrix2f R_mat;
165 R_mat << l_config.r_vel, 0,
166 0, l_config.r_ang;
167
168 if(l_config.test) {
169 chassis.setPose(trajectory[0].x / INCH_TO_METER, trajectory[0].y / INCH_TO_METER, M_PI_2 - trajectory[0].heading, true);
170 } else if(l_config.turnFirst) {
171 double targetH = lemlib::radToDeg(M_PI_2 - trajectory[0].heading);
172 chassis.turnToHeading(l_config.backwards ? targetH + 180 : targetH, 1000);
173 }
174
175 std::vector<std::string> logs;
176 int trajectory_size = trajectory.size();
177
178 double dt = 0.01;
179
180 for (int i = 0; i < trajectory_size; ++i) {
181 uint32_t start_time_ms = pros::millis();
182 const auto &target_state = trajectory[i];
183
184 lemlib::Pose current_pose = chassis.getPose(true);
185 current_pose.x *= INCH_TO_METER;
186 current_pose.y *= INCH_TO_METER;
187 current_pose.theta = M_PI_2 - current_pose.theta;
188
189 double targetHeadingAdjusted = target_state.heading + (l_config.backwards ? M_PI : 0);
190 double errorTheta = angleError(current_pose.theta, targetHeadingAdjusted);
191
192 Eigen::Vector3d global_error;
193 global_error << target_state.x - current_pose.x, target_state.y - current_pose.y, errorTheta;
194
195 Eigen::Matrix3d rotation_matrix;
196 rotation_matrix << std::cos(current_pose.theta), std::sin(current_pose.theta), 0,
197 -std::sin(current_pose.theta), std::cos(current_pose.theta), 0,
198 0, 0, 1;
199
200 Eigen::Vector3d error = rotation_matrix * global_error;
201
202 float v_ref = target_state.linear_vel * (l_config.backwards ? -1.0 : 1.0);
203 float w_ref = target_state.angular_vel;
204
205 float v_calc = v_ref;
206 if (std::abs(v_calc) < 0.25) {
207 v_calc = (v_calc >= 0) ? 0.25 : -0.25;
208 }
209
210 Eigen::Matrix3f A;
211 A << 0, 0, 0,
212 0, 0, v_calc,
213 0, 0, 0;
214
215 Eigen::Matrix<float, 3, 2> B;
216 B << 1, 0,
217 0, 0,
218 0, 1;
219
220 auto discAB = discretizeAB(A, B, dt);
221
222 Eigen::MatrixXf X = dareSolver(discAB.first, discAB.second, Q_mat, R_mat);
223
224 Eigen::MatrixXf K = (R_mat + discAB.second.transpose() * X * discAB.second).inverse() * discAB.second.transpose() * X * discAB.first;
225
226 Eigen::Vector2f u = K * error.cast<float>();
227
228 float v_cmd = v_ref + u(0);
229 float w_cmd = w_ref + u(1);
230
231 float left_actual_mps = leftMotors.get_actual_velocity() * rpm_to_mps_factor;
232 float right_actual_mps = rightMotors.get_actual_velocity() * rpm_to_mps_factor;
233
234 DrivetrainVoltages output_voltages = controller.update(
235 v_cmd,
236 w_cmd,
237 left_actual_mps,
238 right_actual_mps
239 );
240
241 rightMotors.move_voltage(output_voltages.rightVoltage * 1000.0);
242 leftMotors.move_voltage(output_voltages.leftVoltage * 1000.0);
243
244 if(l_config.log) {
245 std::ostringstream ss;
246 ss << Vector2(current_pose.x, current_pose.y).latex() << ",";
247 logs.push_back(ss.str());
248 }
249
250 pros::Task::delay_until(&start_time_ms, 10);
251 }
252
253 if(!l_config.test && l_config.end_correction) {
254 pros::delay(100);
255 chassis.moveToPose(
256 trajectory.back().x / INCH_TO_METER,
257 trajectory.back().y / INCH_TO_METER,
258 lemlib::radToDeg(M_PI_2 - trajectory.back().heading),
259 2000,
260 {.lead = l_config.mpose_lead}
261 );
262 chassis.waitUntilDone();
263 }
264
265 rightMotors.brake();
266 leftMotors.brake();
267
268 if(l_config.log) {
269 for (const auto& line : logs) {
270 std::cout << line;
271 pros::delay(50);
272 }
273 }
274}
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