-
Notifications
You must be signed in to change notification settings - Fork 2
Feature/pure pursuit #37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
28a21e7
870115f
408e0e9
05c5cd6
87ccded
53ac05b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| #ifndef PURE_PURSUIT_H | ||
| #define PURE_PURSUIT_H | ||
|
|
||
| #include <vector> | ||
| #include <string> | ||
| #include <memory> | ||
| #include <atomic> | ||
| #include <thread> | ||
| #include <fstream> | ||
| #include <sstream> | ||
| #include <cmath> | ||
| #include <iostream> | ||
|
|
||
| #include "tank_drive.h" | ||
| #include "odometry.h" // needs to provide pose {x, y, theta} | ||
|
|
||
| // Structure for a single path point | ||
| struct PathPoint { | ||
| double x; | ||
| double y; | ||
| double heading; | ||
| double velocity; | ||
| }; | ||
|
|
||
| // Asynchronous Adaptive Pure Pursuit Controller | ||
| class AsyncAdaptivePurePursuitController { | ||
| public: | ||
| AsyncAdaptivePurePursuitController(TankDrive* drive, std::shared_ptr<Odometry> odom); | ||
| ~AsyncAdaptivePurePursuitController(); | ||
|
|
||
| // Path management | ||
| void setPath(const std::vector<PathPoint>& newPath); | ||
| void setPathFromCSV(const std::string& filename); | ||
|
|
||
| // Control | ||
| void start(); | ||
| void stop(); | ||
| bool isRunning() const; | ||
|
|
||
| private: | ||
| TankDrive* drive; | ||
| std::shared_ptr<Odometry> odom; | ||
| std::vector<PathPoint> path; | ||
|
|
||
| std::atomic<bool> running; | ||
| std::thread controlThread; | ||
|
|
||
| // Internal helpers | ||
| void runLoop(); | ||
| std::vector<PathPoint> loadPathFromCSV(const std::string& filename); | ||
| PathPoint findLookaheadPoint(const Pose& robotPose, double lookaheadDist); | ||
| double computeCurvature(const Pose& robotPose, const PathPoint& target); | ||
| double clamp(double value, double minVal, double maxVal); | ||
| }; | ||
|
|
||
| #endif | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,133 @@ | ||||||||||||||||||||||||||||||||
| #include "purepursuit.h" | ||||||||||||||||||||||||||||||||
| #include <iostream> | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| AsyncAdaptivePurePursuitController::AsyncAdaptivePurePursuitController(TankDrive* drive, std::shared_ptr<Odometry> odom) | ||||||||||||||||||||||||||||||||
| : drive(drive), odom(odom) {} | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| AsyncAdaptivePurePursuitController::~AsyncAdaptivePurePursuitController() { | ||||||||||||||||||||||||||||||||
| stop(); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| void AsyncAdaptivePurePursuitController::setPath(const std::vector<PathPoint>& newPath) { | ||||||||||||||||||||||||||||||||
| path = newPath; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| void AsyncAdaptivePurePursuitController::setPathFromCSV(const std::string& filename) { | ||||||||||||||||||||||||||||||||
| path = loadPathFromCSV(filename); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| bool AsyncAdaptivePurePursuitController::isRunning() const { | ||||||||||||||||||||||||||||||||
| return running.load(); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| void AsyncAdaptivePurePursuitController::start() { | ||||||||||||||||||||||||||||||||
| if (running) return; | ||||||||||||||||||||||||||||||||
| running = true; | ||||||||||||||||||||||||||||||||
| controlThread = std::thread(&AsyncAdaptivePurePursuitController::runLoop, this); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| void AsyncAdaptivePurePursuitController::stop() { | ||||||||||||||||||||||||||||||||
| running = false; | ||||||||||||||||||||||||||||||||
| if (controlThread.joinable()) controlThread.join(); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| std::vector<PathPoint> AsyncAdaptivePurePursuitController::loadPathFromCSV(const std::string& filename) { | ||||||||||||||||||||||||||||||||
| std::vector<PathPoint> path; | ||||||||||||||||||||||||||||||||
| std::ifstream file(filename); | ||||||||||||||||||||||||||||||||
| std::string line; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if (!file.is_open()) { | ||||||||||||||||||||||||||||||||
| std::cerr << "[PurePursuit] Failed to open CSV file: " << filename << std::endl; | ||||||||||||||||||||||||||||||||
| return path; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| while (std::getline(file, line)) { | ||||||||||||||||||||||||||||||||
| std::stringstream ss(line); | ||||||||||||||||||||||||||||||||
| std::string xStr, yStr; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if (!std::getline(ss, xStr, ',')) continue; | ||||||||||||||||||||||||||||||||
| if (!std::getline(ss, yStr, ',')) continue; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| double x = std::stod(xStr); | ||||||||||||||||||||||||||||||||
| double y = std::stod(yStr); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| path.push_back({x, y, 0.0, 0.0}); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Compute headings | ||||||||||||||||||||||||||||||||
| for (size_t i = 0; i + 1 < path.size(); ++i) { | ||||||||||||||||||||||||||||||||
| double dx = path[i + 1].x - path[i].x; | ||||||||||||||||||||||||||||||||
| double dy = path[i + 1].y - path[i].y; | ||||||||||||||||||||||||||||||||
| path[i].heading = std::atan2(dy, dx); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Assign velocities | ||||||||||||||||||||||||||||||||
| for (auto &p : path) | ||||||||||||||||||||||||||||||||
| p.velocity = 1.0; // 1 m/s default, tune later | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| std::cout << "[PurePursuit] Loaded path with " << path.size() << " points." << std::endl; | ||||||||||||||||||||||||||||||||
| return path; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| PathPoint AsyncAdaptivePurePursuitController::findLookaheadPoint(const Pose& robotPose, double lookaheadDist) { | ||||||||||||||||||||||||||||||||
| if (path.empty()) return {0, 0, 0, 0}; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Simple nearest-point + lookahead approach | ||||||||||||||||||||||||||||||||
| double minDist = 1e9; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
| int nearestIdx = 0; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| for (int i = 0; i < static_cast<int>(path.size()); ++i) { | ||||||||||||||||||||||||||||||||
| double dx = path[i].x - robotPose.x; | ||||||||||||||||||||||||||||||||
| double dy = path[i].y - robotPose.y; | ||||||||||||||||||||||||||||||||
| double dist = std::sqrt(dx * dx + dy * dy); | ||||||||||||||||||||||||||||||||
| if (dist < minDist) { | ||||||||||||||||||||||||||||||||
| minDist = dist; | ||||||||||||||||||||||||||||||||
| nearestIdx = i; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| int lookaheadIdx = std::min(nearestIdx + 5, (int)path.size() - 1); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
| int lookaheadIdx = std::min(nearestIdx + 5, (int)path.size() - 1); | |
| // Find the first point at least lookaheadDist away from the robot pose, starting from nearestIdx | |
| int lookaheadIdx = nearestIdx; | |
| double accumDist = 0.0; | |
| for (int i = nearestIdx; i < static_cast<int>(path.size()) - 1; ++i) { | |
| double dx = path[i + 1].x - path[i].x; | |
| double dy = path[i + 1].y - path[i].y; | |
| accumDist += std::sqrt(dx * dx + dy * dy); | |
| if (accumDist >= lookaheadDist) { | |
| lookaheadIdx = i + 1; | |
| break; | |
| } | |
| } | |
| // Clamp to last point if we run out of path | |
| lookaheadIdx = std::min(lookaheadIdx, static_cast<int>(path.size()) - 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Forward declaration would be sufficient here since the class only stores a pointer to
TankDrive. Replace the include withclass TankDrive;to reduce compilation dependencies and improve build times.