Skip to content

initial controls program#1

Open
aborjigin wants to merge 2 commits intomainfrom
amber
Open

initial controls program#1
aborjigin wants to merge 2 commits intomainfrom
amber

Conversation

@aborjigin
Copy link
Copy Markdown
Contributor

It needs some tweaking
Need help from Vision team

Copilot AI review requested due to automatic review settings April 29, 2026 00:25
@aborjigin aborjigin requested a review from AkshajGG April 29, 2026 00:25
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces an initial ROS 2 (ament_cmake) controls package (somars_controls) for PX4 offboard flight, including target localization from vision detections, guidance setpoint generation, and an offboard/arming heartbeat manager.

Changes:

  • Added three ROS 2 C++ nodes: target_localizer, guidance_node, and offboard_manager.
  • Added package scaffolding (CMake/package.xml), launch file, and shared runtime parameters YAML.
  • Expanded README with architecture + quick start instructions.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/target_localizer.cpp Implements pixel-to-ground-plane projection into NED using PX4 attitude/position.
include/somars_controls/target_localizer.hpp Declares TargetLocalizer node interface and camera/vehicle state.
src/guidance_node.cpp Implements target-following guidance and publishes PX4 trajectory setpoints.
include/somars_controls/guidance_node.hpp Declares GuidanceNode node interface, state, and parameters.
src/offboard_manager.cpp Implements offboard heartbeat publishing and optional auto-arm/offboard commands.
include/somars_controls/offboard_manager.hpp Declares OffboardManager node interface and command helpers.
launch/controls.launch.py Launches the three nodes with a shared params file.
config/params.yaml Provides default parameters for all nodes.
CMakeLists.txt Adds build/install rules for the new nodes and configs.
package.xml Declares ROS package metadata and dependencies.
README.md Documents architecture, usage, and dependencies.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/target_localizer.cpp
Comment on lines +140 to +144
Eigen::Matrix3d TargetLocalizer::quat_to_rotation(
double w, double x, double y, double z)
{
return Eigen::Quaterniond(w, x, y, z).normalized().toRotationMatrix();
}
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quat_to_rotation() is declared/defined but never used anywhere in this package. This adds dead code and can confuse future maintenance; either remove it or use it consistently (e.g., in attitude_cb / pixel_to_ned).

Copilot uses AI. Check for mistakes.
Comment thread src/offboard_manager.cpp
Comment on lines +46 to +49
auto period = std::chrono::duration<double>(1.0 / heartbeat_rate_hz_);
heartbeat_timer_ = this->create_wall_timer(
std::chrono::duration_cast<std::chrono::nanoseconds>(period),
std::bind(&OffboardManager::heartbeat_loop, this));
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

heartbeat_rate_hz_ is used as a divisor to compute the timer period. If this parameter is set to 0 or negative, 1.0 / heartbeat_rate_hz_ becomes inf/invalid and can break timer creation. Validate that heartbeat_rate_hz_ > 0 (and fail fast with a clear log) before creating the timer.

Copilot uses AI. Check for mistakes.
Comment thread src/offboard_manager.cpp
Comment on lines +84 to +93
// 2. Hold-position setpoint (keeps PX4 happy while no guidance is active).
// The guidance_node will overwrite this once it starts publishing.
px4_msgs::msg::TrajectorySetpoint sp{};
sp.timestamp = ts;
sp.position[0] = 0.0f;
sp.position[1] = 0.0f;
sp.position[2] = -10.0f; // 10 m AGL (NED)
sp.yaw = 0.0f;
setpoint_pub_->publish(sp);

Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This node continuously publishes a hard-coded hold-position setpoint to /fmu/in/trajectory_setpoint while guidance_node also publishes to the same topic. With two publishers, PX4 will see interleaved setpoints (origin hold vs guidance), which can cause unstable/incorrect flight behavior. Use a single setpoint publisher (recommended), or gate the hold-setpoint publishing (e.g., only before guidance is active, or behind a parameter/topic that indicates an active guidance source).

Copilot uses AI. Check for mistakes.
Comment thread src/guidance_node.cpp
Comment on lines +51 to +55
auto period = std::chrono::duration<double>(1.0 / rate_hz);
control_timer_ = this->create_wall_timer(
std::chrono::duration_cast<std::chrono::nanoseconds>(period),
std::bind(&GuidanceNode::control_loop, this));

Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

control_rate_hz is used as a divisor to compute the control loop period. If it is configured as 0 or negative, the timer period becomes inf/invalid and timer creation can fail. Add validation that rate_hz > 0 (and fail fast with a clear log) before creating the timer.

Copilot uses AI. Check for mistakes.
Comment thread src/guidance_node.cpp
Comment on lines +111 to +115
if (!position_received_) {
// Nothing we can do until we know where we are.
setpoint_pub_->publish(sp);
return;
}
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When position_received_ is false, this publishes a TrajectorySetpoint with default-initialized position[] (zeros). With OffboardControlMode.position = true, that can command the vehicle toward (0,0,0) before any local position has been received. Either avoid publishing a position setpoint until position is known, or explicitly set sp.position[] to NaN (and/or adjust OffboardControlMode) so PX4 ignores it.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nodes do not need to publish every loop, and should not publish meaningless data.

Comment thread src/guidance_node.cpp
Comment on lines +139 to +143
// Move towards target at approach_speed_, staying at loiter altitude
Eigen::Vector3d direction = to_target.normalized();
double step = std::min(approach_speed_ * 0.5, dist); // half-second lookahead
desired_pos = vehicle_position_ned_ + direction * step;
desired_pos.z() = loiter_altitude_;
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The step calculation uses a hard-coded 0.5 seconds (approach_speed_ * 0.5) even though the control loop rate is configurable. At 20 Hz this makes the commanded position jump far too quickly (effectively multiplying speed), and at low rates it slows down. Compute the step using the actual control loop dt (e.g., approach_speed_ / rate_hz) or store the timer period and use it here.

Copilot uses AI. Check for mistakes.
Comment thread package.xml
<depend>std_msgs</depend>
<depend>sensor_msgs</depend>

<build_depend>eigen</build_depend>
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<build_depend>eigen</build_depend> is not a standard ROS 2 package dependency (Eigen is typically declared as eigen3 / eigen3_cmake_module). This can break rosdep resolution and CI builds. Update the manifest to use the correct Eigen package dependency names consistent with find_package(Eigen3 REQUIRED) in CMake.

Suggested change
<build_depend>eigen</build_depend>
<build_depend>eigen3</build_depend>

Copilot uses AI. Check for mistakes.
Comment thread src/target_localizer.cpp Outdated
Comment on lines +120 to +121
Eigen::Vector3d ray_cam((u - cx_) / fx_, (v - cy_) / fy_, 1.0);
ray_cam.normalize();
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

camera_fx/camera_fy are used as divisors when computing the normalized ray. If either parameter is configured as 0 (or very close to 0), this will generate inf/NaN rays and break localization. Add parameter validation (e.g., reject non-positive values with a clear log + throw/early return) before using them.

Copilot uses AI. Check for mistakes.
@Kyle-Eldridge Kyle-Eldridge self-requested a review April 30, 2026 16:33
Copy link
Copy Markdown

@Kyle-Eldridge Kyle-Eldridge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good first version of the code, but there are several major bugs that will prevent it from working.

Comment thread src/guidance_node.cpp Outdated
Comment on lines +117 to +118
// Expire targets older than 2 seconds
bool target_stale = (this->now() - last_target_time_).seconds() > 2.0;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The target does not move, so this could be longer. Instead, adding something like a debouncer would help filter out false targets. It could either be implemented here, in the target callback, or in the target localization node.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line could be completely removed, since we might see a target while doing another task but not want to fly to it yet.

Comment thread src/guidance_node.cpp Outdated
Comment on lines +129 to +137
if (dist < acceptance_radius_) {
// Close enough – hold above the target at loiter altitude
desired_pos = Eigen::Vector3d(
target_position_ned_.x(),
target_position_ned_.y(),
loiter_altitude_);

RCLCPP_INFO_THROTTLE(this->get_logger(), *this->get_clock(), 2000,
"Within acceptance radius (%.1f m) – holding position", dist);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to either drop something on the target or land on it. We can keep code similar to this to decide when we are close, but then we need to go to the correct height and send a signal to dop the payload.

Comment thread src/guidance_node.cpp Outdated
Comment on lines +139 to +141
// Move towards target at approach_speed_, staying at loiter altitude
Eigen::Vector3d direction = to_target.normalized();
double step = std::min(approach_speed_ * 0.5, dist); // half-second lookahead
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how the autonomous controller works, but could we just update the desired pose to be directly above the target? As it gets closer, this desired pose will update, probably becoming more exact.

Comment thread src/guidance_node.cpp
Comment on lines +155 to +158
// ---- No active target – hold current XY at loiter altitude ----
sp.position[0] = static_cast<float>(vehicle_position_ned_.x());
sp.position[1] = static_cast<float>(vehicle_position_ned_.y());
sp.position[2] = static_cast<float>(loiter_altitude_);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The competition gives us a position where we should be able to see the target. If we haven't seen a target yet, we should go there.

Comment thread src/guidance_node.cpp
Comment on lines +165 to +167

setpoint_pub_->publish(sp);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This node should not always control the drone. Either this node should not always publish or the main node should ignore its messages until it's ready to drop the payload. After dropping, this node should be ignored again.

Comment thread src/target_localizer.cpp Outdated
Comment on lines +119 to +120
// 1. Pixel → normalised camera-frame ray
Eigen::Vector3d ray_cam((u - cx_) / fx_, (v - cy_) / fy_, 1.0);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be done on the pi/Jetson. Currently, it sends the data as angles, but this calculation is simpler and better for this use. I will change the somars-vision code to send this data instead.

Comment thread src/target_localizer.cpp Outdated
Comment on lines +127 to +132
// 3. Intersect with ground plane (NED z = 0).
// Drone is at vehicle_position_ned_.z() (negative when above ground).
// We need the ray to point downward (positive z in NED) to hit ground.
if (ray_ned.z() <= 1e-6) {
return false; // ray is parallel to or pointing away from ground
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought down was defined as negative in NED, but this returns false for negative z.

Comment thread src/target_localizer.cpp Outdated
Comment on lines +24 to +29
// Build the fixed camera-to-body rotation.
// Assumes the camera optical axis is mounted in the body XZ-plane,
// pitched by cam_pitch about the body Y-axis.
// body X = forward, Y = right, Z = down (FRD)
// camera Z = optical axis (into scene)
R_cam_to_body_ = Eigen::AngleAxisd(cam_pitch, Eigen::Vector3d::UnitY()).toRotationMatrix();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this rotation is wrong. The vector [0, 0, 1] in the camera frame should be rotated π/2 if the pitch is 0 to become [1, 0, 0]. You might have to do something like pitch+M_PI_2, depending on the convention for rotation direction.

Comment thread src/target_localizer.cpp Outdated
Comment on lines +135 to +136
target_ned = vehicle_position_ned_ + t * ray_ned;
target_ned.z() = 0.0; // on the ground plane
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the calculation was correct, this second line is not necessary. Maybe check that it is approximately 0 and either throw or log an error if not.

Comment thread src/target_localizer.cpp
Comment on lines +152 to +157
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<somars_controls::TargetLocalizer>());
rclcpp::shutdown();
return 0;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See earlier comment about exceptions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants