Skip to content

Robot Substitution#3429

Closed
Mr-Anyone wants to merge 4 commits into
UBC-Thunderbots:masterfrom
Mr-Anyone:robot_substitution
Closed

Robot Substitution#3429
Mr-Anyone wants to merge 4 commits into
UBC-Thunderbots:masterfrom
Mr-Anyone:robot_substitution

Conversation

@Mr-Anyone

@Mr-Anyone Mr-Anyone commented Feb 20, 2025

Copy link
Copy Markdown
Contributor

Description

Robot substitution

Testing Done

TBD, maybe need a field potentially!

Length Justification and Key Files to Review

N/A

Review Checklist

  • Function & Class comments: All function definitions (usually in the .h file) should have a javadoc style comment at the start of them. For examples, see the functions defined in thunderbots/software/geom. Similarly, all classes should have an associated Javadoc comment explaining the purpose of the class.
  • Remove all commented out code
  • Remove extra print statements: for example, those just used for testing
  • Resolve all TODO's: All TODO (or similar) statements should either be completed or associated with a github issue

Taken mostly from @jameschuang2002 in his pr.

Comment thread src/software/ai/hl/stp/play/play.cpp Outdated

// move to middle of court and to positive y boundary and stop
auto_sub_tactic->updateControlParams(
Point(0, world_ptr->field().totalYLength() / 2), Angle::zero(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we need to be able to control which boundary to go to. Ideally, via dynamic params

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

bump @Mr-Anyone

Comment thread src/software/ai/hl/stp/play/play.cpp Outdated
TbotsProto::MaxAllowedSpeedMode::PHYSICAL_LIMIT,
TbotsProto::ObstacleAvoidanceMode::SAFE);

for (auto robot : injured_robots)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we need to limit the number of robots that will simultaneously substitute (ideally a max number set via dynamic params)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

bump @Mr-Anyone

asst));
}

TEST_F(TacticAssignmentTest, test_assigning_stop_tactics_to_unassigned_non_goalie_robots)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can you add this test file to the BUILD

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

bump @Mr-Anyone


virtual ~SensorFusion() = default;

const static int BREAK_BEAM_TOLERANCE = 2;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

please suffix units to all constants

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

bump @Mr-Anyone

Comment on lines +1150 to +1298
TEST_F(SensorFusionTest, test_detect_front_right_motor_fault)
{
std::vector<TbotsProto::RobotStatus> robot_status_msgs;
auto robot_msg = std::make_unique<TbotsProto::RobotStatus>();
robot_msg->set_robot_id(1);
robot_msg->add_error_code(TbotsProto::ErrorCode::NO_ERROR);
robot_msg->mutable_motor_status()->mutable_front_right()->add_motor_faults(
TbotsProto::MotorFault::DRIVER_OVERTEMPERATURE);
robot_status_msgs.push_back(*robot_msg);

auto robot_msg2 = std::make_unique<TbotsProto::RobotStatus>();
robot_msg2->add_error_code(TbotsProto::ErrorCode::NO_ERROR);
robot_msg2->set_robot_id(2);
robot_status_msgs.push_back(*robot_msg2);

SensorProto sensor_msg;

auto ssl_wrapper_packet =
createSSLWrapperPacket(std::move(geom_data), initDetectionFrame());
// set vision msg so that world is valid
*(sensor_msg.mutable_ssl_vision_msg()) = *ssl_wrapper_packet;

sensor_fusion.processSensorProto(sensor_msg);

sensor_fusion.detectInjuredRobots(robot_status_msgs);

std::vector<Robot> injured_robots =
sensor_fusion.getWorld().value().friendlyTeam().getInjuredRobots();
EXPECT_EQ(injured_robots.size(), 1);

if (injured_robots.size() == 1)
{
EXPECT_EQ(injured_robots[0].id(), 1);
}
}

TEST_F(SensorFusionTest, test_detect_front_left_motor_fault)
{
std::vector<TbotsProto::RobotStatus> robot_status_msgs;
auto robot_msg = std::make_unique<TbotsProto::RobotStatus>();
robot_msg->set_robot_id(1);
robot_msg->add_error_code(TbotsProto::ErrorCode::NO_ERROR);
robot_msg->mutable_motor_status()->mutable_front_left()->add_motor_faults(
TbotsProto::MotorFault::DRIVER_OVERTEMPERATURE_PREWARNING);
robot_status_msgs.push_back(*robot_msg);

auto robot_msg2 = std::make_unique<TbotsProto::RobotStatus>();
robot_msg2->add_error_code(TbotsProto::ErrorCode::NO_ERROR);
robot_msg2->set_robot_id(2);
robot_status_msgs.push_back(*robot_msg2);

SensorProto sensor_msg;

auto ssl_wrapper_packet =
createSSLWrapperPacket(std::move(geom_data), initDetectionFrame());
// set vision msg so that world is valid
*(sensor_msg.mutable_ssl_vision_msg()) = *ssl_wrapper_packet;

sensor_fusion.processSensorProto(sensor_msg);

sensor_fusion.detectInjuredRobots(robot_status_msgs);

std::vector<Robot> injured_robots =
sensor_fusion.getWorld().value().friendlyTeam().getInjuredRobots();
EXPECT_EQ(injured_robots.size(), 1);

if (injured_robots.size() == 1)
{
EXPECT_EQ(injured_robots[0].id(), 1);
}
}

TEST_F(SensorFusionTest, test_detect_back_right_motor_fault)
{
std::vector<TbotsProto::RobotStatus> robot_status_msgs;
auto robot_msg = std::make_unique<TbotsProto::RobotStatus>();
robot_msg->set_robot_id(1);
robot_msg->add_error_code(TbotsProto::ErrorCode::NO_ERROR);
robot_msg->mutable_motor_status()->mutable_front_right()->add_motor_faults(
TbotsProto::MotorFault::DRIVER_OVERTEMPERATURE);
robot_status_msgs.push_back(*robot_msg);

auto robot_msg2 = std::make_unique<TbotsProto::RobotStatus>();
robot_msg2->add_error_code(TbotsProto::ErrorCode::NO_ERROR);
robot_msg2->set_robot_id(2);
robot_msg2->mutable_motor_status()->mutable_back_right()->add_motor_faults(
TbotsProto::MotorFault::DRIVER_OVERTEMPERATURE);
robot_status_msgs.push_back(*robot_msg2);

SensorProto sensor_msg;

auto ssl_wrapper_packet =
createSSLWrapperPacket(std::move(geom_data), initDetectionFrame());
// set vision msg so that world is valid
*(sensor_msg.mutable_ssl_vision_msg()) = *ssl_wrapper_packet;

sensor_fusion.processSensorProto(sensor_msg);

sensor_fusion.detectInjuredRobots(robot_status_msgs);

std::vector<Robot> injured_robots =
sensor_fusion.getWorld().value().friendlyTeam().getInjuredRobots();
EXPECT_EQ(injured_robots.size(), 2);

if (injured_robots.size() == 2)
{
EXPECT_EQ(injured_robots[0].id(), 1);
EXPECT_EQ(injured_robots[1].id(), 2);
}
}

TEST_F(SensorFusionTest, test_detect_back_left_motor_fault)
{
std::vector<TbotsProto::RobotStatus> robot_status_msgs;
auto robot_msg = std::make_unique<TbotsProto::RobotStatus>();
robot_msg->set_robot_id(1);
robot_msg->add_error_code(TbotsProto::ErrorCode::NO_ERROR);
robot_msg->mutable_motor_status()->mutable_front_left()->add_motor_faults(
TbotsProto::MotorFault::DRIVER_OVERTEMPERATURE_PREWARNING);
robot_status_msgs.push_back(*robot_msg);

auto robot_msg2 = std::make_unique<TbotsProto::RobotStatus>();
robot_msg2->add_error_code(TbotsProto::ErrorCode::NO_ERROR);
robot_msg2->set_robot_id(2);
robot_msg2->mutable_motor_status()->mutable_back_left()->add_motor_faults(
TbotsProto::MotorFault::DRIVER_OVERTEMPERATURE_PREWARNING);
robot_status_msgs.push_back(*robot_msg2);

SensorProto sensor_msg;

auto ssl_wrapper_packet =
createSSLWrapperPacket(std::move(geom_data), initDetectionFrame());
// set vision msg so that world is valid
*(sensor_msg.mutable_ssl_vision_msg()) = *ssl_wrapper_packet;

sensor_fusion.processSensorProto(sensor_msg);

sensor_fusion.detectInjuredRobots(robot_status_msgs);

std::vector<Robot> injured_robots =
sensor_fusion.getWorld().value().friendlyTeam().getInjuredRobots();
EXPECT_EQ(injured_robots.size(), 2);

if (injured_robots.size() == 2)
{
EXPECT_EQ(injured_robots[0].id(), 1);
EXPECT_EQ(injured_robots[1].id(), 2);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

these tests are all very similar. consider making this into a parametrized test (TEST_P)

example:

TEST_P(CSVSinkTest, test_csv_log_levels_not_logging)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

bump @Mr-Anyone

dribble_displacement = std::nullopt;
}

// This detects injured robots, but isn't called anywhere?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

looks like you can call this function in:

void SensorFusion::updateWorld(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

bump @Mr-Anyone

@github-actions

Copy link
Copy Markdown
Contributor

This PR is stale because it has been open for 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.

@github-actions github-actions Bot added the Stale Inactive pull requests label Apr 19, 2025
@github-actions

Copy link
Copy Markdown
Contributor

This PR was closed because it has been stalled for 5 days with no activity.

@github-actions github-actions Bot closed this Apr 24, 2025
@itsarune itsarune reopened this Apr 29, 2025
@github-actions github-actions Bot removed the Stale Inactive pull requests label Apr 30, 2025
@Mr-Anyone Mr-Anyone marked this pull request as draft May 15, 2025 22:11
@Mr-Anyone Mr-Anyone force-pushed the robot_substitution branch from fc6997b to f2b8a13 Compare May 31, 2025 06:46
Comment thread src/software/ai/hl/stp/play/play.cpp Outdated
if (injured_robots.size() > 0)
{
std::map<RobotId, TrajectoryPath> robot_trajectories = {};
unsigned int num_injured = (unsigned int)injured_robots.size();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

don't use C-style casts. static_cast<unsigned int>(injured_robots.size())

void SensorFusion::detectInjuredRobots(
const std::vector<TbotsProto::RobotStatus> &robot_status_msgs)
{
/* potential improvement: add short circuit logic when an error is detected */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

make an issue for this

RobotId robot_id = robot_status_msg.robot_id();
bool has_error = false;

// Note: each check is not continuing when the robot is identified as injured

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Note: each check is not continuing when the robot is identified as injured
// Note: check stops once a robot has been identified as injured

drive_unit_msgs.push_back(motor_status_msgs.front_left());
drive_unit_msgs.push_back(motor_status_msgs.front_right());
drive_unit_msgs.push_back(motor_status_msgs.back_left());
drive_unit_msgs.push_back(motor_status_msgs.back_right());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

you can also add the dribbler as well

virtual ~SensorFusion() = default;

const static int BREAK_BEAM_TOLERANCE = 2;
void detectInjuredRobots(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

docs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

also this function can be private


std::vector<Robot> injured_robots =
sensor_fusion.getWorld().value().friendlyTeam().getInjuredRobots();
EXPECT_EQ(injured_robots.size(), 1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

kinda pedantic but if you use ASSERT_EQ here, then below you can remove the if because the assertion will stop execution at this point if it fails


std::vector<Robot> injured_robots =
sensor_fusion.getWorld().value().friendlyTeam().getInjuredRobots();
EXPECT_EQ(injured_robots.size(), 2);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same note as above

sensor_fusion.getWorld().value().friendlyTeam().getInjuredRobots();
EXPECT_EQ(injured_robots.size(), 1);

if (injured_robots.size() == 1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same note as above


std::vector<Robot> injured_robots =
sensor_fusion.getWorld().value().friendlyTeam().getInjuredRobots();
EXPECT_EQ(injured_robots.size(), 1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same note

sensor_fusion.getWorld().value().friendlyTeam().getInjuredRobots();
EXPECT_EQ(injured_robots.size(), 1);

if (injured_robots.size() == 1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same note

@itsarune itsarune left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

great work with the tests! left some comments

@Mr-Anyone

Copy link
Copy Markdown
Contributor Author

great work with the tests! left some comments

It turns out that the code taken previously doesn't really work after running the test case. I will work on this in a few days.

std::vector<RobotSubstituionParam> substitution_robot_params
= trySubstituteInjuredRobots(world_ptr);
// applying robot substitution result
num_tactics -= static_cast<unsigned int>(substitution_robot_params.size());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

prone to underflow:

num_tactics = std::max(0, static_cast<int>(num_tactics) - substitution_robot_params.size());

std::vector<Play::RobotSubstituionParam> Play::trySubstituteInjuredRobots(const WorldPtr& world_ptr){
// Don't do anything if there aren't any injured robots
const std::vector<Robot>& injured_robots = world_ptr->friendlyTeam().getInjuredRobots();
if(injured_robots.size() == 0)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

using explicit { } is a part of our code style guide:

* Always use curly braces around code blocks, even if the braces surround a single statement.


// move to middle of court and to positive y boundary and stop
double y_coordinate = world_ptr->field().totalYLength() / 2.0;
if(!ai_config.robot_substitution_config().substitute_at_positive_y())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

using explicit { } is a part of our code style guide:

* Always use curly braces around code blocks, even if the braces surround a single statement.


private:
// For the implementation of `trySubstituteInjuredRobots`
struct RobotSubstituionParam{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
struct RobotSubstituionParam{
struct RobotSubstitutionParam{

* Remove the substituted robot from robots
*
* @param world_ptr: the current world_ptr with the robot
* @param robots substituted robots are removed from this list. There is side effect

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this param doesn't exist

virtual void updateTactics(const PlayUpdate& play_update);

private:
// For the implementation of `trySubstituteInjuredRobots`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
// For the implementation of `trySubstituteInjuredRobots`
/**
* RobotSubstitutionParam contains the replacement tactics for robot substitutions.

EXPECT_EQ(expected_pos, injured_robot_pos);
}

/**

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

you should also check that we don't exceed the limit we've set in the maximum_robot_substitution_count()


// substitution tactic is just a move tactic to a decided location
std::shared_ptr<MoveTactic> auto_sub_tactic = std::make_shared<MoveTactic>();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Robot Substitution rules

We should also check that the ball is > 0.5m away from the robot substitution location before committing to the substitution

@@ -0,0 +1,71 @@

TEST_F(TacticAssignmentTest, test_assigning_stop_tactics_to_unassigned_non_goalie_robots)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@itsarune itsarune left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

left some feedback

@Andrewyx

Andrewyx commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

@Mr-Anyone What the status of this issue? If it is not currently in progress, could you create an issue, note your progress then link this PR? That way I can assign this issue to someone/prune this ticket

@Mr-Anyone

Copy link
Copy Markdown
Contributor Author

@Mr-Anyone What the status of this issue? If it is not currently in progress, could you create an issue, note your progress then link this PR? That way I can assign this issue to someone/prune this ticket

I just took a look at this PR, and it seems that if you call setInjuredRobots, the robot will be put in substitution by removing it from the play assignment?

Nobodies calls detectInjuredRobots, so this PR has no functional change. I think this PR just adds the ability for robot substitution (by calling setInjuredRobots?), but never use it in anywhere? Also, I am pretty sure breakbeam change in this PR is a duplicate of #3206. The original robot substitution did not land before we made it to the Netherland. You probably want to remove the detectInjuredRobots function.

tbh, it may be easier to redo this PR than to build from it (I think you can do it more elegantly than this). Feel free to assign it to people. I took most of the code from #3091 .

@Mr-Anyone Mr-Anyone closed this Apr 7, 2026
@Mr-Anyone

Mr-Anyone commented Apr 8, 2026

Copy link
Copy Markdown
Contributor Author

@Andrewyx @williamckha btw, feel free to take over #3455 if you think it is urgent or just want to assign it for someone to do.

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