Skip to content

Commit af00683

Browse files
committed
first draft of assignment done
1 parent 2e9560d commit af00683

File tree

4 files changed

+108
-85
lines changed

4 files changed

+108
-85
lines changed

pages/admission_assignments/asd_admission_assignment.mdx

Lines changed: 108 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ So how do you use ROS? Learning ROS (and robotics programming in general) takes
269269
### ROS System Diagram
270270
Here's a simple diagram of three ROSnodes talking to each other. This is the generic way most robotics developers look at robotics systems.
271271

272-
![]()
272+
![](../../public/assets/simple_ros_diagram.png)
273273

274274
### ROS Node
275275
A process that is running instances of ROS objects is called a ROSnode. What this means is, as long as your code has ROS objects that listen/publish data to the network, then it is considered a ROSnode. You can think of ROSnodes as the building blocks of a robotics system.
@@ -297,7 +297,7 @@ In the warmup, we made you write a simple publisher. Please use the warmup code
297297
### The Sky's the Limit
298298
A robotics system can be composed of a bunch of different ROSnodes communcating in a bunch of different ways. In fact, a ROSnode can consist of multiple publishers and subscribers. You just need to be careful of the concurrency between them.
299299

300-
![]()
300+
![](../../public/assets/limitless_potential.png)
301301

302302
Unfortunately, the only way to truly understand ROS is to read their docs and use it. If you have any questions when programming ROS, please ask them in our Discord.
303303

@@ -374,7 +374,7 @@ From before, you know that this robot will need the following high-level modules
374374
### A Simple Navigation Architecture
375375
Below is a diagram of a simple navigation architecture that can be used to help the robot navigate from point A to point B.
376376

377-
![]()
377+
![](../../public/assets/assignment_arch.png)
378378

379379
Here is a description of each node:
380380
- **Costmap:** A ROSnode that takes in LaserScans from the `/lidar` topic, and converts them into a costmap. This is a discretized grid of squares that represent the chances as object exists in that grid with an arbitrary score.
@@ -859,6 +859,7 @@ private:
859859
> - 1 Subscriber that subscribes to the '/path' topic for [nav_msgs::msg::Path](https://docs.ros.org/en/lunar/api/nav_msgs/html/msg/Path.html) messages
860860
> - 1 Subscriber that subscribes to the '/odom/filtered' topic for [nav_msgs::msg::Odometry](https://docs.ros.org/en/noetic/api/nav_msgs/html/msg/Odometry.html) messages
861861
> - 1 Publisher that publishes [geometry_msgs::msg::Twist](https://docs.ros.org/en/lunar/api/geometry_msgs/html/msg/Twist.html) messages to a '/costmap' topic
862+
> - 1 Timer to follow a stored path
862863
863864
![]()
864865
@@ -880,130 +881,152 @@ The lookahead distance controls how far ahead the target point is chosen. A larg
880881
881882
You can read more about Pure Pursuit [here](https://www.mathworks.com/help/nav/ug/pure-pursuit-controller.html).
882883
883-
---
884-
885-
#### **Node Behavior**
886-
887-
The control node continuously tracks the robot's position relative to the path and calculates how the robot should move to reach the next "lookahead point." The Pure Pursuit algorithm determines the steering curvature needed to bring the robot closer to this point, ensuring smooth and dynamic path tracking.
888-
889-
#### **Core Functionalities**
884+
#### **Node Behavior and Design**
890885
891-
1. **Path Tracking**:
892-
- Subscribes to `/path` (`nav_msgs::msg::Path`) for the global path generated by the planner.
893-
- Selects a "lookahead point" on the path that lies a fixed distance ahead of the robot.
886+
The Pure Pursuit Controller node is responsible for taking a path and odometry data as inputs and generating velocity commands to guide the robot along the path. The node operates in a closed-loop fashion: it continuously checks the robot's current position, calculates the desired heading based on the "lookahead point," and generates appropriate linear and angular velocity commands.
894887
895-
2. **Robot Pose Tracking**:
896-
- Subscribes to `/odom/filtered` (`nav_msgs::msg::Odometry`) to get the robot's current position and orientation.
888+
The node subscribes to:
889+
- **`/path`**: Receives the global path as a series of waypoints.
890+
- **`/odom/filtered`**: Tracks the robot's current position and orientation.
897891
898-
3. **Twist Calculation**:
899-
- Computes linear and angular velocities:
900-
- Linear velocity is proportional to the distance to the lookahead point.
901-
- Angular velocity is determined based on the curvature required to reach the lookahead point.
902-
- Publishes the velocity commands as a `geometry_msgs::msg::Twist` message to the `/cmd_vel` topic.
892+
The node publishes:
893+
- **`/cmd_vel`**: Outputs velocity commands in the form of linear and angular velocities.
903894
904-
4. **Dynamic Behavior**:
905-
- Continuously updates the lookahead point and recalculates velocities as the robot moves.
895+
Additionally, the node employs a timer to ensure regular updates of the velocity commands.
906896
907897
---
908898
909-
#### **Key Pure Pursuit Components**
899+
#### **Key Functionalities**
900+
901+
1. **Path Tracking**:
902+
- Extract the current lookahead point from the path based on the robot’s position and a fixed lookahead distance.
903+
- Compute the steering angle required to reach the lookahead point.
910904
911-
- **Lookahead Point**: A target point chosen on the path at a fixed distance ahead of the robot.
912-
- **Steering Angle**: Computed based on the curvature of the circular arc connecting the robot to the lookahead point.
913-
- **Curvature**: Determines the angular velocity needed to follow the circular arc.
905+
2. **Velocity Commands**:
906+
- Calculate linear and angular velocities using the steering angle and the robot’s dynamics.
914907
915-
The steering curvature is calculated as:
916-
```math
917-
\kappa = \frac{2 \cdot y}{L_d^2}
918-
```
919-
- y: Lateral error (perpendicular distance to the lookahead point).
920-
- L_d: Lookahead distance.
908+
3. **Error Handling**:
909+
- Handle edge cases like when the path is empty or the robot is close to the final goal.
921910
922-
The angular velocity is then:
923-
```math
924-
\omega = v \cdot \kappa
925-
```
926-
- v: Linear velocity (proportional to L_d).
911+
4. **Timer for Updates**:
912+
- The node updates the velocity commands at a fixed rate (e.g., 10 Hz) for smooth and consistent control.
927913
928914
---
929915
930-
#### **Pseudo-ROS Node Implementation**
916+
#### **Pseudo ROS Code Example**
931917
932918
```cpp
933-
class ControlNode : public rclcpp::Node {
919+
#include <rclcpp/rclcpp.hpp>
920+
#include <nav_msgs/msg/path.hpp>
921+
#include <nav_msgs/msg/odometry.hpp>
922+
#include <geometry_msgs/msg/twist.hpp>
923+
#include <cmath>
924+
#include <optional>
925+
926+
class PurePursuitController : public rclcpp::Node {
934927
public:
935-
ControlNode() : Node("control_node"), lookahead_distance_(1.0), max_linear_velocity_(0.5) {
936-
// Subscribers
928+
PurePursuitController() : Node("pure_pursuit_controller") {
929+
// Initialize parameters
930+
lookahead_distance_ = 1.0; // Lookahead distance
931+
goal_tolerance_ = 0.1; // Distance to consider the goal reached
932+
linear_speed_ = 0.5; // Constant forward speed
933+
934+
// Subscribers and Publishers
937935
path_sub_ = this->create_subscription<nav_msgs::msg::Path>(
938-
"/path", 10, std::bind(&ControlNode::pathCallback, this, std::placeholders::_1));
936+
"/path", 10, [this](const nav_msgs::msg::Path::SharedPtr msg) { current_path_ = msg; });
937+
939938
odom_sub_ = this->create_subscription<nav_msgs::msg::Odometry>(
940-
"/odom/filtered", 10, std::bind(&ControlNode::odomCallback, this, std::placeholders::_1));
939+
"/odom/filtered", 10, [this](const nav_msgs::msg::Odometry::SharedPtr msg) { robot_odom_ = msg; });
941940
942-
// Publisher
943941
cmd_vel_pub_ = this->create_publisher<geometry_msgs::msg::Twist>("/cmd_vel", 10);
944942
945-
RCLCPP_INFO(this->get_logger(), "Control node initialized");
943+
// Timer
944+
control_timer_ = this->create_wall_timer(
945+
std::chrono::milliseconds(100), [this]() { controlLoop(); });
946946
}
947947
948948
private:
949-
// Subscribers and Publisher
950-
rclcpp::Subscription<nav_msgs::msg::Path>::SharedPtr path_sub_;
951-
rclcpp::Subscription<nav_msgs::msg::Odometry>::SharedPtr odom_sub_;
952-
rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr cmd_vel_pub_;
949+
void controlLoop() {
950+
// Skip control if no path or odometry data is available
951+
if (!current_path_ || !robot_odom_) {
952+
return;
953+
}
953954
954-
// Data storage
955-
nav_msgs::msg::Path current_path_;
956-
geometry_msgs::msg::Pose current_pose_;
955+
// Find the lookahead point
956+
auto lookahead_point = findLookaheadPoint();
957+
if (!lookahead_point) {
958+
return; // No valid lookahead point found
959+
}
957960
958-
// Parameters
959-
double lookahead_distance_;
960-
double max_linear_velocity_;
961+
// Compute velocity command
962+
auto cmd_vel = computeVelocity(*lookahead_point);
961963
962-
void pathCallback(const nav_msgs::msg::Path::SharedPtr msg) {
963-
current_path_ = *msg;
964-
RCLCPP_INFO(this->get_logger(), "Path received with %zu poses", current_path_.poses.size());
964+
// Publish the velocity command
965+
cmd_vel_pub_->publish(cmd_vel);
965966
}
966967
967-
void odomCallback(const nav_msgs::msg::Odometry::SharedPtr msg) {
968-
current_pose_ = msg->pose.pose;
969-
computeAndPublishTwist();
968+
std::optional<geometry_msgs::msg::PoseStamped> findLookaheadPoint() {
969+
// TODO: Implement logic to find the lookahead point on the path
970+
return std::nullopt; // Replace with a valid point when implemented
970971
}
971972
972-
void computeAndPublishTwist() {
973-
if (current_path_.poses.empty()) {
974-
RCLCPP_WARN(this->get_logger(), "No path available");
975-
return;
976-
}
977-
978-
geometry_msgs::msg::PoseStamped lookahead_point = findLookaheadPoint();
979-
if (!lookahead_point.header.frame_id.empty()) {
980-
geometry_msgs::msg::Twist cmd_vel;
981-
calculatePurePursuit(lookahead_point, cmd_vel);
982-
cmd_vel_pub_->publish(cmd_vel);
983-
} else {
984-
RCLCPP_WARN(this->get_logger(), "No valid lookahead point found");
985-
}
973+
geometry_msgs::msg::Twist computeVelocity(const geometry_msgs::msg::PoseStamped &target) {
974+
// TODO: Implement logic to compute velocity commands
975+
geometry_msgs::msg::Twist cmd_vel;
976+
return cmd_vel;
986977
}
987978
988-
geometry_msgs::msg::PoseStamped findLookaheadPoint() {
989-
// TODO: Implement lookahead point selection logic
990-
return geometry_msgs::msg::PoseStamped(); // Placeholder
979+
double computeDistance(const geometry_msgs::msg::Point &a, const geometry_msgs::msg::Point &b) {
980+
// TODO: Implement distance calculation between two points
981+
return 0.0;
991982
}
992983
993-
void calculatePurePursuit(const geometry_msgs::msg::PoseStamped &lookahead_point,
994-
geometry_msgs::msg::Twist &cmd_vel) {
995-
// TODO: Implement Pure Pursuit control logic
984+
double extractYaw(const geometry_msgs::msg::Quaternion &quat) {
985+
// TODO: Implement quaternion to yaw conversion
986+
return 0.0;
996987
}
988+
989+
// Subscribers and Publishers
990+
rclcpp::Subscription<nav_msgs::msg::Path>::SharedPtr path_sub_;
991+
rclcpp::Subscription<nav_msgs::msg::Odometry>::SharedPtr odom_sub_;
992+
rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr cmd_vel_pub_;
993+
994+
// Timer
995+
rclcpp::TimerBase::SharedPtr control_timer_;
996+
997+
// Data
998+
nav_msgs::msg::Path::SharedPtr current_path_;
999+
nav_msgs::msg::Odometry::SharedPtr robot_odom_;
1000+
1001+
// Parameters
1002+
double lookahead_distance_;
1003+
double goal_tolerance_;
1004+
double linear_speed_;
9971005
};
1006+
1007+
// Main function
1008+
int main(int argc, char **argv) {
1009+
rclcpp::init(argc, argv);
1010+
auto node = std::make_shared<PurePursuitController>();
1011+
rclcpp::spin(node);
1012+
rclcpp::shutdown();
1013+
return 0;
1014+
}
9981015
```
9991016

10001017
---
10011018

10021019
#### **Key Details**
1003-
1. **Dynamic Path Tracking**: Continuously adjusts to changes in the path and robot's position.
1004-
2. **Smooth Control**: The Pure Pursuit algorithm ensures smooth motion by steering toward the lookahead point.
1005-
3. **Lookahead Distance**: A tunable parameter that balances tracking accuracy and smoothness.
1006-
4. **Twist Output**: Publishes both linear and angular velocities, which can be consumed by the robot's hardware abstraction layer (in our case, our simulation).
1020+
1021+
- **Lookahead Distance**: The controller selects a point on the path at a fixed distance ahead of the robot. This distance must be tuned for the robot's dynamics and desired path-following performance.
1022+
1023+
- **Linear Speed**: The robot’s speed is typically kept constant, while the angular velocity adjusts based on the curvature.
1024+
1025+
- **Edge Cases**: Ensure the node handles scenarios like an empty path or reaching the final goal. You can stop the robot when the goal is within the `goal_tolerance`.
1026+
1027+
- **Yaw Calculation**: Converting the robot’s quaternion orientation to yaw is essential for computing the steering angle.
1028+
1029+
This implementation provides a simple yet effective way to integrate Pure Pursuit Control in ROS for smooth path following.
10071030

10081031
## Helpful Tips
10091032
- You never need to run `colcon build` because our monorepo infrastructure runs it automatically whenever you run `./watod build`. A good amount of ROS packaging and Launchfiles has also been abstracted away from you. This was deliberate in order to make your experience with the assignment as "code-heavy" as possible.

public/assets/assignment_arch.png

1.01 MB
Loading

public/assets/limitless_potential.png

74.3 KB
Loading

public/assets/simple_ros_diagram.png

181 KB
Loading

0 commit comments

Comments
 (0)