Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ add_executable(turret_bot_main turret_bot_main.cc)
target_link_libraries(turret_bot_main PRIVATE ${OpenCV_LIBS} apriltag ntcore camera nlohmann_json::nlohmann_json Eigen3::Eigen localization utils yolo gamepiece)

add_executable(main_bot_main main_bot_main.cc)
target_link_libraries(main_bot_main PRIVATE ${OpenCV_LIBS} apriltag ntcore camera nlohmann_json::nlohmann_json Eigen3::Eigen localization utils)
target_link_libraries(main_bot_main PRIVATE ${OpenCV_LIBS} apriltag ntcore camera nlohmann_json::nlohmann_json Eigen3::Eigen localization utils controller)

set(CMAKE_CXX_CLANG_TIDY "")
44 changes: 40 additions & 4 deletions src/main_bot_main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "src/localization/opencv_apriltag_detector.h"
#include "src/localization/run_localization.h"
#include "src/localization/square_solver.h"
#include "src/pathing/controller.h"
#include "src/utils/camera_utils.h"
#include "src/utils/nt_utils.h"

Expand All @@ -14,12 +15,16 @@ auto main() -> int {
utils::StartNetworktables();
// TODO configure vision bot camera paths

std::string log_path = frc::DataLogManager::GetLogDir();
// NT for enabling/disabling pathing controller
auto instance = nt::NetworkTableInstance::GetDefault();
auto table = instance.GetTable("Pathing");
auto enabled_entry = table->GetEntry("Enabled");

LOG(INFO) << "Starting cameras with right camera disabled";
LOG(INFO) << "Starting cameras";
// camera::CameraSource front_camera = camera::CameraSource(
// "Front", std::make_unique<camera::CVCamera>(
// camera_constants[Camera::MAIN_ROBOT_FRONT_CAMERA]));
// "front",
// std::make_unique<camera::CVCamera>(
// camera::camera_constants[camera::Camera::MAIN_ROBOT_FRONT_CAMERA]));

camera::CameraSource left_camera = camera::CameraSource(
"Left", std::make_unique<camera::CVCamera>(
Expand Down Expand Up @@ -71,4 +76,35 @@ auto main() -> int {
LOG(INFO) << "Started estimators";

left_thread.join();
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

right_thread (created before this block) is never joined or detached, while only left_thread is joined at line 78. This means if the program ever terminates abnormally, the destructor of a joinable std::thread will call std::terminate. The thread should be either joined (alongside left_thread) or detached.

Suggested change
left_thread.join();
left_thread.join();
right_thread.join(); // Ensure right_thread is also joined before destruction

Copilot uses AI. Check for mistakes.

// Main control loop for pathing controller
std::unique_ptr<pathing::Controller> controller;
std::thread controller_thread;
bool last_enabled = false;

LOG(INFO) << "Entering main control loop";

while (true) {
Comment on lines 78 to +87
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

The pathing control loop (starting at line 87) is placed after left_thread.join() at line 78. Since RunLocalization runs an infinite loop (the localization threads never terminate normally), left_thread.join() will block forever and the pathing control loop will never execute. The pathing logic should be started before joining threads, or the join should be restructured (e.g., using detach() or running the control loop in its own thread).

Copilot uses AI. Check for mistakes.
bool enabled = enabled_entry.GetBoolean(false);

if (enabled && !last_enabled) {
LOG(INFO) << "Starting pathing controller";
controller = std::make_unique<pathing::Controller>();
controller_thread = std::thread([&controller]() { controller->Send(); });
} else if (!enabled && last_enabled) {
LOG(INFO) << "Stopping pathing controller";
if (controller) {
controller->Stop();
if (controller_thread.joinable()) {
controller_thread.join();
}
controller.reset();
}
}

last_enabled = enabled;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}

std::this_thread::sleep_for(std::chrono::hours::max());
Comment on lines +108 to +109
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

The line std::this_thread::sleep_for(std::chrono::hours::max()); at line 109 is unreachable code because the while(true) loop above it at line 87 never exits (no break exists in the loop). This dead code should be removed.

Suggested change
std::this_thread::sleep_for(std::chrono::hours::max());

Copilot uses AI. Check for mistakes.
}
8 changes: 7 additions & 1 deletion src/pathing/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
add_executable(simulator simulator.cc pathing.cc)
target_link_libraries(simulator PRIVATE ${OpenCV_LIBS} nlohmann_json::nlohmann_json ntcore wpilibc wpimath)
target_link_libraries(simulator PRIVATE ${OpenCV_LIBS} nlohmann_json::nlohmann_json ntcore wpilibc wpimath)

add_library(controller controller.cc velocity_sender.cc pathing.cc)
target_link_libraries(controller PRIVATE ${OpenCV_LIBS} nlohmann_json::nlohmann_json ntcore wpilibc wpimath)

add_executable(pathing_controller_main controller_main.cc)
target_link_libraries(pathing_controller_main PRIVATE controller ntcore wpilibc wpimath nlohmann_json::nlohmann_json ${OpenCV_LIBS})
Comment on lines +7 to +8
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

controller_main.cc is referenced as a source file for the pathing_controller_main executable but this file does not exist in the repository. The build will fail when CMake tries to compile this target.

Suggested change
add_executable(pathing_controller_main controller_main.cc)
target_link_libraries(pathing_controller_main PRIVATE controller ntcore wpilibc wpimath nlohmann_json::nlohmann_json ${OpenCV_LIBS})
# The pathing_controller_main target was removed because controller_main.cc does not exist in the repository.

Copilot uses AI. Check for mistakes.
135 changes: 135 additions & 0 deletions src/pathing/controller.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#include "src/pathing/controller.h"
#include <chrono>
#include <fstream>
#include <nlohmann/json.hpp>
#include <thread>
#include "src/pathing/pathing.h"
#include "src/pathing/velocity_sender.h"

namespace pathing {

Controller::Controller() : instance_(nt::NetworkTableInstance::GetDefault()) {

auto table = instance_.GetTable("Pathing");

current_pose_sub_ =
table->GetStructTopic<frc::Pose2d>("CurrentPose").Subscribe({});

target_pose_sub_ =
table->GetStructTopic<frc::Pose2d>("TargetPose").Subscribe({});
}

void Controller::Send() {

VelocitySender sender;

std::ifstream file("/root/bos/constants/navgrid.json");
nlohmann::json data = nlohmann::json::parse(file);
Comment thread
nanoticity marked this conversation as resolved.

int W = data["grid"][0].size();
int H = data["grid"].size();
double nodeSize = data["nodeSizeMeters"];

std::vector<std::vector<bool>> gridData(H, std::vector<bool>(W));

for (int y = 0; y < H; y++)
for (int x = 0; x < W; x++)
gridData[y][x] = data["grid"][y][x];

cv::Mat grid = initializeGrid(gridData);

frc::Pose2d start = current_pose_sub_.Get();
frc::Pose2d target = target_pose_sub_.Get();

int sx = start.X().value() / nodeSize;
int sy = start.Y().value() / nodeSize;
int tx = target.X().value() / nodeSize;
int ty = target.Y().value() / nodeSize;

auto poses = createSpline(grid, sx, sy, tx, ty, nodeSize);

if (poses.empty()) {
sender.Send(0, 0);
running_.store(false);
return;
}

std::vector<double> dist(poses.size(), 0);

for (size_t i = 1; i < poses.size(); i++) {
double dx = poses[i].X().value() - poses[i - 1].X().value();
double dy = poses[i].Y().value() - poses[i - 1].Y().value();
dist[i] = dist[i - 1] + std::hypot(dx, dy);
}

double total = dist.back();

constexpr double maxV = 5.0;
constexpr double maxA = 3.0;
constexpr double lookahead = 0.5;
constexpr double tol = 0.1;

std::vector<double> speed(poses.size());

for (size_t i = 0; i < poses.size(); i++) {
double a = std::sqrt(2 * maxA * dist[i]);
double d = std::sqrt(2 * maxA * (total - dist[i]));
speed[i] = std::min({a, d, maxV});
}

size_t idx = 0;

Comment thread
nanoticity marked this conversation as resolved.
while (running_) {

frc::Pose2d pose = current_pose_sub_.Get();
double rx = pose.X().value();
double ry = pose.Y().value();

double goalDist = std::hypot(poses.back().X().value() - rx,
poses.back().Y().value() - ry);

if (goalDist < tol) {
sender.Send(0, 0);
break;
}

for (size_t i = idx; i < poses.size(); i++) {
double d =
std::hypot(poses[i].X().value() - rx, poses[i].Y().value() - ry);
if (d >= lookahead) {
idx = i;
break;
}
}
Comment on lines +96 to +103
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

The lookahead point search at lines 96–103 only advances idx when a pose is at least lookahead distance away, but it never resets idx when a new Send() call is made (it's a local size_t idx = 0 initialized once per Send() call, which is fine). However, the inner for loop can fail to find any pose beyond lookahead distance (e.g., when the robot is already past all nearby poses), in which case idx is unchanged from its last value. The guard if (idx >= poses.size()) idx = poses.size() - 1; on line 105 only catches an out-of-bounds index, not a stale idx pointing to an already-passed waypoint. This could cause the controller to continually target a waypoint behind the robot, creating oscillation. Consider advancing idx to the closest not-yet-visited waypoint as a fallback.

Copilot uses AI. Check for mistakes.

if (idx >= poses.size())
idx = poses.size() - 1;
Comment thread
nanoticity marked this conversation as resolved.

double txp = poses[idx].X().value();
double typ = poses[idx].Y().value();

double dx = txp - rx;
double dy = typ - ry;
double mag = std::hypot(dx, dy);

if (mag < 1e-6) {
sender.Send(0, 0);
if (!running_)
break;
continue;
}

double vx = dx / mag * speed[idx];
double vy = dy / mag * speed[idx];

sender.Send(vx, vy);

std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
}

void Controller::Stop() {
running_.store(false);
}

} // namespace pathing
22 changes: 22 additions & 0 deletions src/pathing/controller.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

#include <frc/geometry/Pose2d.h>
#include <networktables/NetworkTableInstance.h>
#include <networktables/StructTopic.h>
#include <atomic>
#include <cstdint>
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

<cstdint> is included in controller.h but is not used anywhere in either controller.h or controller.cc. This unused include should be removed.

Suggested change
#include <cstdint>

Copilot uses AI. Check for mistakes.

namespace pathing {
class Controller {
public:
Controller();
void Send();
void Stop();

private:
nt::NetworkTableInstance instance_;
nt::StructSubscriber<frc::Pose2d> current_pose_sub_;
nt::StructSubscriber<frc::Pose2d> target_pose_sub_;
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

running_ is used in controller.cc (lines 53, 82, 117, 132) as an std::atomic member but it is never declared in the Controller class definition in controller.h. This will cause a compilation failure. An std::atomic<bool> running_{true}; member must be added to the private section of the Controller class.

Suggested change
nt::StructSubscriber<frc::Pose2d> target_pose_sub_;
nt::StructSubscriber<frc::Pose2d> target_pose_sub_;
std::atomic<bool> running_{true}; // Controls the running state of the controller loop

Copilot uses AI. Check for mistakes.
};

} // namespace pathing
26 changes: 26 additions & 0 deletions src/pathing/velocity_sender.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#include "src/pathing/velocity_sender.h"
#include <frc/Timer.h>
#include <networktables/DoubleTopic.h>
#include <networktables/StructTopic.h>
Comment thread
nanoticity marked this conversation as resolved.

namespace pathing {

VelocitySender::VelocitySender()
: instance_(nt::NetworkTableInstance::GetDefault()) {
std::shared_ptr<nt::NetworkTable> table =
instance_.GetTable("Orin/OTFVelocity/");

nt::DoubleArrayTopic vel_topic = table->GetDoubleArrayTopic("Velocity");
vel_publisher_ = vel_topic.Publish();
}

void VelocitySender::Send(const double ax, const double ay) {
if (mutex_.try_lock()) {
double timestamp = frc::Timer::GetFPGATimestamp().to<double>() +
instance_.GetServerTimeOffset().value_or(0) / 1000000.0;
std::vector<double> vel_array = {ax, ay, timestamp};
vel_publisher_.Set(vel_array);
mutex_.unlock();
}
Comment thread
nanoticity marked this conversation as resolved.
Comment on lines +18 to +24
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

VelocitySender::Send uses mutex_.try_lock() which silently drops velocity commands if the mutex is already held. Since Send() is called only from the single Controller::Send() thread in the current design, there is no other concurrent caller to contend with, making the non-blocking try_lock unnecessary. More importantly, if the lock genuinely can't be acquired, velocity commands are silently lost — the robot will continue at its last commanded velocity, which is a safety concern. Using a blocking std::lock_guard<std::mutex> (as done in the analogous PositionSender::Send) would be safer and more consistent with the rest of the codebase.

Copilot uses AI. Check for mistakes.
}
} // namespace pathing
25 changes: 25 additions & 0 deletions src/pathing/velocity_sender.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once

#include <frc/geometry/Pose2d.h>
#include <networktables/BooleanTopic.h>
#include <networktables/DoubleArrayTopic.h>
#include <networktables/DoubleTopic.h>
#include <networktables/NetworkTable.h>
#include <networktables/NetworkTableInstance.h>
#include <networktables/StructTopic.h>
Comment on lines +3 to +9
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

velocity_sender.h includes several headers that are not used in the class itself: <frc/geometry/Pose2d.h>, <networktables/BooleanTopic.h>, <networktables/DoubleTopic.h>, <networktables/NetworkTable.h>, and <networktables/StructTopic.h>. Only <networktables/DoubleArrayTopic.h>, <networktables/NetworkTableInstance.h>, and <mutex> (from pch.h) are actually needed. Unused includes increase compile times and create unnecessary coupling.

Suggested change
#include <frc/geometry/Pose2d.h>
#include <networktables/BooleanTopic.h>
#include <networktables/DoubleArrayTopic.h>
#include <networktables/DoubleTopic.h>
#include <networktables/NetworkTable.h>
#include <networktables/NetworkTableInstance.h>
#include <networktables/StructTopic.h>
#include <networktables/DoubleArrayTopic.h>
#include <networktables/NetworkTableInstance.h>

Copilot uses AI. Check for mistakes.
#include "src/utils/pch.h"

namespace pathing {
class VelocitySender {
public:
VelocitySender();
void Send(const double ax, const double ay);

private:
nt::NetworkTableInstance instance_;

nt::DoubleArrayPublisher vel_publisher_;

std::mutex mutex_;
};
} // namespace pathing