From ec7010a9f2ef29a8078267ea44df9ece55fc5ea7 Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Sat, 1 Nov 2025 12:24:29 -0700 Subject: [PATCH 01/34] Added NeverExcessivelyDribbles functionality into OffensePlayTest --- src/software/ai/hl/stp/play/offense/offense_play_test.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/software/ai/hl/stp/play/offense/offense_play_test.py b/src/software/ai/hl/stp/play/offense/offense_play_test.py index 6b1e4bef1a..9ab35d6d34 100644 --- a/src/software/ai/hl/stp/play/offense/offense_play_test.py +++ b/src/software/ai/hl/stp/play/offense/offense_play_test.py @@ -4,6 +4,8 @@ import software.python_bindings as tbots_cpp from proto.play_pb2 import Play, PlayName + +from software.simulated_tests.excessive_dribbling import NeverExcessivelyDribbles from software.simulated_tests.friendly_team_scored import * from software.simulated_tests.ball_enters_region import * from software.simulated_tests.friendly_has_ball_possession import * @@ -77,9 +79,12 @@ def setup(start_point): # Always Validation inv_always_validation_sequence_set = [ - [BallAlwaysStaysInRegion(regions=[field.fieldBoundary()])] + [BallAlwaysStaysInRegion(regions=[field.fieldBoundary()])], + [NeverExcessivelyDribbles()] ] + + ag_always_validation_sequence_set = [[FriendlyAlwaysHasBallPossession()]] # Eventually Validation From b690a45e70aed9b6d744ee8bc67a874a49596ad3 Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Sat, 29 Nov 2025 21:11:07 -0800 Subject: [PATCH 02/34] intermediate commit --- src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py diff --git a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py new file mode 100644 index 0000000000..e69de29bb2 From c339388c403cd0e2b16e652d5479a4a7b78bbcaf Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Sat, 29 Nov 2025 21:11:50 -0800 Subject: [PATCH 03/34] intermediate change --- src/software/ai/hl/stp/play/offense/BUILD | 4 +- .../hl/stp/play/offense/offense_play_test.py | 2 +- src/software/ai/hl/stp/tactic/dribble/BUILD | 20 +++++ .../tactic/dribble/excessive_dribble_test.py | 80 +++++++++++++++++++ .../simulated_tests/excessive_dribbling.py | 17 ++-- 5 files changed, 112 insertions(+), 11 deletions(-) diff --git a/src/software/ai/hl/stp/play/offense/BUILD b/src/software/ai/hl/stp/play/offense/BUILD index 4e2658fa63..53f6282dc5 100644 --- a/src/software/ai/hl/stp/play/offense/BUILD +++ b/src/software/ai/hl/stp/play/offense/BUILD @@ -29,9 +29,9 @@ cc_library( ) py_test( - name = "offense_play_test", + name = "excessive_dribble_test", srcs = [ - "offense_play_test.py", + "excessive_dribble_test.py", ], # TODO (#2619) Remove tag to run in parallel tags = [ diff --git a/src/software/ai/hl/stp/play/offense/offense_play_test.py b/src/software/ai/hl/stp/play/offense/offense_play_test.py index 9ab35d6d34..4cc0fe0efc 100644 --- a/src/software/ai/hl/stp/play/offense/offense_play_test.py +++ b/src/software/ai/hl/stp/play/offense/offense_play_test.py @@ -105,4 +105,4 @@ def setup(start_point): if __name__ == "__main__": # Run the test, -s disables all capturing at -vv increases verbosity - sys.exit(pytest.main([__file__, "-svv"])) + sys.exit(pytest.main([__file__, "-s"])) diff --git a/src/software/ai/hl/stp/tactic/dribble/BUILD b/src/software/ai/hl/stp/tactic/dribble/BUILD index 3c5565efb3..2351832276 100644 --- a/src/software/ai/hl/stp/tactic/dribble/BUILD +++ b/src/software/ai/hl/stp/tactic/dribble/BUILD @@ -1,3 +1,5 @@ +load("@simulated_tests_deps//:requirements.bzl", "requirement") + package(default_visibility = ["//visibility:public"]) cc_library( @@ -60,3 +62,21 @@ cc_test( "//software/world", ], ) + +py_test( + name = "excessive_dribble_test", + srcs = [ + "excessive_dribble_test.py", + ], + # TODO (#2619) Remove tag to run in parallel + tags = [ + "exclusive", + ], + deps = [ + "//software:conftest", + "//software/simulated_tests:speed_threshold_helpers", + "//software/simulated_tests:validation", + requirement("pytest"), + ], +) + diff --git a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py index e69de29bb2..bb70d47ce7 100644 --- a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py +++ b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py @@ -0,0 +1,80 @@ +import pytest + +import software.python_bindings as tbots_cpp +from software.simulated_tests.robot_enters_region import * +from software.simulated_tests.ball_enters_region import * +from software.simulated_tests.ball_moves_in_direction import * +from software.simulated_tests.friendly_has_ball_possession import * +from software.simulated_tests.ball_speed_threshold import * +from software.simulated_tests.robot_speed_threshold import * +from software.simulated_tests.excessive_dribbling import * +from software.simulated_tests.simulated_test_fixture import ( + pytest_main, +) +from proto.message_translation.tbots_protobuf import create_world_state +from proto.ssl_gc_common_pb2 import Team + +@pytest.mark.parametrize( + "dribble_destination,final_dribble_orientation,allow_excessive_dribbling", + [ + # Dribble Destination for the ball > 1.0 from its starting position + (tbots_cpp.Point(1.012, 0), tbots_cpp.Angle(), True), + ], +) + +def test_excessive_dribbling( + dribble_destination, + final_dribble_orientation, + allow_excessive_dribbling, + simulated_test_runner, +): + simulated_test_runner.simulator_proto_unix_io.send_proto( + WorldState, + create_world_state( + [], + blue_robot_locations=[tbots_cpp.Point(1, 0)], + ball_location=tbots_cpp.Point(0, 0), + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + + # Setup Tactic + params = AssignedTacticPlayControlParams() + params.assigned_tactics[0].dribble.CopyFrom( + DribbleTactic( + dribble_destination=tbots_cpp.createPointProto(dribble_destination), + final_dribble_orientation=tbots_cpp.createAngleProto(final_dribble_orientation), + allow_excessive_dribbling=allow_excessive_dribbling, + ) + ) + simulated_test_runner.blue_full_system_proto_unix_io.send_proto( + AssignedTacticPlayControlParams, params + ) + + # Setup no tactics on the enemy side + params = AssignedTacticPlayControlParams() + simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( + AssignedTacticPlayControlParams, params + ) + + # Always Validation + always_validation_sequence_set = [ + [BallAlwaysStaysInRegion( + regions=[tbots_cpp.Circle(tbots_cpp.Point(0, 0), 1)] + )], + [NeverExcessivelyDribbles()] + ] + + # Eventually Validation + eventually_validation_sequence_set = [[]] + + simulated_test_runner.run_test( + inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + inv_always_validation_sequence_set=always_validation_sequence_set, + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + ag_always_validation_sequence_set=always_validation_sequence_set, + ) + + +if __name__ == "__main__": + pytest_main(__file__) diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/excessive_dribbling.py index 750dbc5900..4d15438fed 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/excessive_dribbling.py @@ -22,19 +22,20 @@ def get_validation_status(self, world) -> ValidationStatus: PASSING when the robot is not excessively dribbling """ ball_position = tbots_cpp.createPoint(world.ball.current_state.global_position) + ball = world.friendly_team.team_robots for robot in world.friendly_team.team_robots: - if not tbots_cpp.Robot(robot).isNearDribbler(ball_position, 0.01): + if not tbots_cpp.Robot(robot).isNearDribbler(ball_position, 0.02): # if ball is not near dribbler then de-activate this validation self.continous_dribbling_start_point = None - elif ( - ball_position - (self.continous_dribbling_start_point or ball_position) - ).length() > 1.0: - return ValidationStatus.FAILING - elif self.continous_dribbling_start_point is None: - # ball is in dribbler, but previously wasn't in dribbler, so set continuous dribbling start point - self.continous_dribbling_start_point = ball_position + else: + if self.continous_dribbling_start_point is None: + self.continous_dribbling_start_point = ball_position + print(ball_position) + if (ball_position - self.continous_dribbling_start_point).length() > 1: + return ValidationStatus.FAILING return ValidationStatus.PASSING + def get_validation_geometry(self, world) -> ValidationGeometry: """(override) Shows the max allowed dribbling circle""" return create_validation_geometry( From a4f6bc04bef061ad4268ca966641bee0861ae923 Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Sat, 29 Nov 2025 23:01:17 -0800 Subject: [PATCH 04/34] Switched to world proto subscription measurement --- .../hl/stp/play/offense/offense_play_test.py | 2 -- .../tactic/dribble/excessive_dribble_test.py | 2 +- .../simulated_tests/excessive_dribbling.py | 19 +++++++++++++++++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/software/ai/hl/stp/play/offense/offense_play_test.py b/src/software/ai/hl/stp/play/offense/offense_play_test.py index 4cc0fe0efc..1fd088580a 100644 --- a/src/software/ai/hl/stp/play/offense/offense_play_test.py +++ b/src/software/ai/hl/stp/play/offense/offense_play_test.py @@ -83,8 +83,6 @@ def setup(start_point): [NeverExcessivelyDribbles()] ] - - ag_always_validation_sequence_set = [[FriendlyAlwaysHasBallPossession()]] # Eventually Validation diff --git a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py index bb70d47ce7..217de69e4d 100644 --- a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py +++ b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py @@ -18,7 +18,7 @@ "dribble_destination,final_dribble_orientation,allow_excessive_dribbling", [ # Dribble Destination for the ball > 1.0 from its starting position - (tbots_cpp.Point(1.012, 0), tbots_cpp.Angle(), True), + (tbots_cpp.Point(1.03, 0), tbots_cpp.Angle(), True), ], ) diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/excessive_dribbling.py index 4d15438fed..4989538de1 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/excessive_dribbling.py @@ -1,5 +1,6 @@ import software.python_bindings as tbots_cpp from proto.import_all_protos import * +from software.thunderscope.thread_safe_buffer import ThreadSafeBuffer from software.simulated_tests.validation import ( Validation, @@ -11,16 +12,30 @@ class ExcessivelyDribbling(Validation): """Checks if any friendly robot is excessively dribbling the ball, i.e. for over 1m.""" - def __init__(self): + def __init__(self, buffer_size: int = 5): self.continous_dribbling_start_point = None - def get_validation_status(self, world) -> ValidationStatus: + self.world_buffer = ThreadSafeBuffer(buffer_size, World) + + def get_validation_status(self, world, max_dribble_length: int = 1) -> ValidationStatus: """Checks if any friendly robot is excessively dribbling the ball, i.e. for over 1m. :param world: The world msg to validate + :param max_dribble_length: the maximum dribble distance allowed (competition 1m) :return: FAILING when the robot is excessively dribbling PASSING when the robot is not excessively dribbling """ + + if world.HasField("dribble_displacement"): + dribble_disp = world.dribble_displacement + dist = tbots_cpp.createSegment(dribble_disp).length() + if dist > max_dribble_length: + return ValidationStatus.FAILING + + return ValidationStatus.PASSING + + + ball_position = tbots_cpp.createPoint(world.ball.current_state.global_position) ball = world.friendly_team.team_robots for robot in world.friendly_team.team_robots: From 544bc55b312ea56c95da80eb91607112247b4849 Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Sun, 30 Nov 2025 02:57:18 -0800 Subject: [PATCH 05/34] Added excessive dribbling functionality and testing --- src/software/ai/hl/stp/play/offense/BUILD | 6 +- .../tactic/dribble/excessive_dribble_test.py | 99 +++++++++++++++++-- .../simulated_tests/excessive_dribbling.py | 27 +---- 3 files changed, 97 insertions(+), 35 deletions(-) diff --git a/src/software/ai/hl/stp/play/offense/BUILD b/src/software/ai/hl/stp/play/offense/BUILD index 53f6282dc5..44119d72b3 100644 --- a/src/software/ai/hl/stp/play/offense/BUILD +++ b/src/software/ai/hl/stp/play/offense/BUILD @@ -29,9 +29,9 @@ cc_library( ) py_test( - name = "excessive_dribble_test", + name = "offense_play_test", srcs = [ - "excessive_dribble_test.py", + "offense_play_test.py", ], # TODO (#2619) Remove tag to run in parallel tags = [ @@ -42,4 +42,4 @@ py_test( "//software/simulated_tests:validation", requirement("pytest"), ], -) +) \ No newline at end of file diff --git a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py index 217de69e4d..3fdb456b99 100644 --- a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py +++ b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py @@ -15,14 +15,27 @@ from proto.ssl_gc_common_pb2 import Team @pytest.mark.parametrize( - "dribble_destination,final_dribble_orientation,allow_excessive_dribbling", + "initial_location,dribble_destination,final_dribble_orientation,allow_excessive_dribbling", [ - # Dribble Destination for the ball > 1.0 from its starting position - (tbots_cpp.Point(1.03, 0), tbots_cpp.Angle(), True), + # Dribble Destination for the ball < 1.0 from its starting position + (tbots_cpp.Point(0.5, 0), tbots_cpp.Point(1.02, 0), tbots_cpp.Angle(), True), + + # Dribble Testing diagonally + (tbots_cpp.Point(0.25, 0.25), tbots_cpp.Point(0.85, 0.65), tbots_cpp.Angle.fromRadians(50), True), + + # Boundary Testing, because of the autoref implementation (initial of position Bot to final of Ball), + # a dribble distance a tiny bit over 1m should pass + (tbots_cpp.Point(0.01, 0), tbots_cpp.Point(1.025, 0), tbots_cpp.Angle(), True), + (tbots_cpp.Point(1, 1.5), tbots_cpp.Point(2, 1.5), tbots_cpp.Angle(), True), + + # When the robot is directly on the ball, the orientation at which the bot begins to + # dribble is different; there appears to be a bit more leeway + (tbots_cpp.Point(0, 1), tbots_cpp.Point(0, 2.025), tbots_cpp.Angle(), True), ], ) -def test_excessive_dribbling( +def test_not_excessive_dribbling( + initial_location, dribble_destination, final_dribble_orientation, allow_excessive_dribbling, @@ -32,12 +45,13 @@ def test_excessive_dribbling( WorldState, create_world_state( [], - blue_robot_locations=[tbots_cpp.Point(1, 0)], - ball_location=tbots_cpp.Point(0, 0), + blue_robot_locations=[tbots_cpp.Point(0, 1)], + ball_location=initial_location, ball_velocity=tbots_cpp.Vector(0, 0), ), ) + # Setup Tactic params = AssignedTacticPlayControlParams() params.assigned_tactics[0].dribble.CopyFrom( @@ -59,9 +73,6 @@ def test_excessive_dribbling( # Always Validation always_validation_sequence_set = [ - [BallAlwaysStaysInRegion( - regions=[tbots_cpp.Circle(tbots_cpp.Point(0, 0), 1)] - )], [NeverExcessivelyDribbles()] ] @@ -76,5 +87,75 @@ def test_excessive_dribbling( ) +@pytest.mark.parametrize( + "initial_location,dribble_destination,final_dribble_orientation,allow_excessive_dribbling", + [ + # Dribble Destination for the ball > 1.0 from its starting position + (tbots_cpp.Point(0, 2), tbots_cpp.Point(0, 0.5), tbots_cpp.Angle(), True), + # Dribble Testing diagonally + (tbots_cpp.Point(0.1, 1.1), tbots_cpp.Point(1.1, 0.1), tbots_cpp.Angle.fromRadians(50), True), + # Boundary Testing, because of the autoref implementation (initial position of Bot to final of Ball), + # a dribble distance a tiny bit over 1m should pass, but too much (> 1.015 from testing) fails + (tbots_cpp.Point(0, 1), tbots_cpp.Point(0, 2.02), tbots_cpp.Angle(), True), + + # When the robot is directly on the ball, the orientation at which the bot begins to + # dribble is different; there appears to be a bit more leeway on the distance that passes + # Furthermore, that leeway appears to be different depending on the position of the bot and ball + # (e.g. here the point is 0, 0, and the leeway is 1 cm more than at 0, 1) + (tbots_cpp.Point(0, 0), tbots_cpp.Point(0, 1.035), tbots_cpp.Angle(), True), + ], +) + +def test_excessive_dribbling( + initial_location, + dribble_destination, + final_dribble_orientation, + allow_excessive_dribbling, + simulated_test_runner, +): + simulated_test_runner.simulator_proto_unix_io.send_proto( + WorldState, + create_world_state( + [], + blue_robot_locations=[tbots_cpp.Point(0, 0.0)], + ball_location=initial_location, + ball_velocity=tbots_cpp.Vector(0, 0), + ), + ) + + + # Setup Tactic + params = AssignedTacticPlayControlParams() + params.assigned_tactics[0].dribble.CopyFrom( + DribbleTactic( + dribble_destination=tbots_cpp.createPointProto(dribble_destination), + final_dribble_orientation=tbots_cpp.createAngleProto(final_dribble_orientation), + allow_excessive_dribbling=allow_excessive_dribbling, + ) + ) + simulated_test_runner.blue_full_system_proto_unix_io.send_proto( + AssignedTacticPlayControlParams, params + ) + + # Setup no tactics on the enemy side + params = AssignedTacticPlayControlParams() + simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( + AssignedTacticPlayControlParams, params + ) + + # Always Validation + always_validation_sequence_set = [[]] + + # Eventually Validation + eventually_validation_sequence_set = [[EventuallyStartsExcessivelyDribbling()]] + + simulated_test_runner.run_test( + inv_eventually_validation_sequence_set=eventually_validation_sequence_set, + inv_always_validation_sequence_set=always_validation_sequence_set, + ag_eventually_validation_sequence_set=eventually_validation_sequence_set, + ag_always_validation_sequence_set=always_validation_sequence_set, + ) + + if __name__ == "__main__": pytest_main(__file__) diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/excessive_dribbling.py index 4989538de1..eaacd15eee 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/excessive_dribbling.py @@ -1,6 +1,5 @@ import software.python_bindings as tbots_cpp from proto.import_all_protos import * -from software.thunderscope.thread_safe_buffer import ThreadSafeBuffer from software.simulated_tests.validation import ( Validation, @@ -12,12 +11,10 @@ class ExcessivelyDribbling(Validation): """Checks if any friendly robot is excessively dribbling the ball, i.e. for over 1m.""" - def __init__(self, buffer_size: int = 5): + def __init__(self): self.continous_dribbling_start_point = None - self.world_buffer = ThreadSafeBuffer(buffer_size, World) - - def get_validation_status(self, world, max_dribble_length: int = 1) -> ValidationStatus: + def get_validation_status(self, world, max_dribble_length: float = 1.00) -> ValidationStatus: """Checks if any friendly robot is excessively dribbling the ball, i.e. for over 1m. :param world: The world msg to validate @@ -25,7 +22,8 @@ def get_validation_status(self, world, max_dribble_length: int = 1) -> Validatio :return: FAILING when the robot is excessively dribbling PASSING when the robot is not excessively dribbling """ - + # Use world calculation of dribbling distance, which uses implementation + # of initial position of BOT to final position of BALL if world.HasField("dribble_displacement"): dribble_disp = world.dribble_displacement dist = tbots_cpp.createSegment(dribble_disp).length() @@ -34,23 +32,6 @@ def get_validation_status(self, world, max_dribble_length: int = 1) -> Validatio return ValidationStatus.PASSING - - - ball_position = tbots_cpp.createPoint(world.ball.current_state.global_position) - ball = world.friendly_team.team_robots - for robot in world.friendly_team.team_robots: - if not tbots_cpp.Robot(robot).isNearDribbler(ball_position, 0.02): - # if ball is not near dribbler then de-activate this validation - self.continous_dribbling_start_point = None - else: - if self.continous_dribbling_start_point is None: - self.continous_dribbling_start_point = ball_position - print(ball_position) - if (ball_position - self.continous_dribbling_start_point).length() > 1: - return ValidationStatus.FAILING - return ValidationStatus.PASSING - - def get_validation_geometry(self, world) -> ValidationGeometry: """(override) Shows the max allowed dribbling circle""" return create_validation_geometry( From d928b5dd543ebf50a1f07d10a2458ea79c3cbd77 Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Sun, 30 Nov 2025 03:16:52 -0800 Subject: [PATCH 06/34] Reverted verbosity change which was for print testing previously --- src/software/ai/hl/stp/play/offense/offense_play_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/software/ai/hl/stp/play/offense/offense_play_test.py b/src/software/ai/hl/stp/play/offense/offense_play_test.py index 1fd088580a..d3d5c95ee1 100644 --- a/src/software/ai/hl/stp/play/offense/offense_play_test.py +++ b/src/software/ai/hl/stp/play/offense/offense_play_test.py @@ -103,4 +103,4 @@ def setup(start_point): if __name__ == "__main__": # Run the test, -s disables all capturing at -vv increases verbosity - sys.exit(pytest.main([__file__, "-s"])) + sys.exit(pytest.main([__file__, "-svv"])) From 32cc42d3a3c2da886da6bd8851331fa092c1a58a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sun, 30 Nov 2025 11:58:52 +0000 Subject: [PATCH 07/34] [pre-commit.ci lite] apply automatic fixes --- src/software/ai/hl/stp/play/offense/BUILD | 2 +- .../hl/stp/play/offense/offense_play_test.py | 2 +- src/software/ai/hl/stp/tactic/dribble/BUILD | 1 - .../tactic/dribble/excessive_dribble_test.py | 56 ++++++++++--------- .../simulated_tests/excessive_dribbling.py | 7 ++- 5 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/software/ai/hl/stp/play/offense/BUILD b/src/software/ai/hl/stp/play/offense/BUILD index 44119d72b3..4e2658fa63 100644 --- a/src/software/ai/hl/stp/play/offense/BUILD +++ b/src/software/ai/hl/stp/play/offense/BUILD @@ -42,4 +42,4 @@ py_test( "//software/simulated_tests:validation", requirement("pytest"), ], -) \ No newline at end of file +) diff --git a/src/software/ai/hl/stp/play/offense/offense_play_test.py b/src/software/ai/hl/stp/play/offense/offense_play_test.py index 9b33a47728..b865ebe2c2 100644 --- a/src/software/ai/hl/stp/play/offense/offense_play_test.py +++ b/src/software/ai/hl/stp/play/offense/offense_play_test.py @@ -79,7 +79,7 @@ def setup(start_point): # Always Validation inv_always_validation_sequence_set = [ [BallAlwaysStaysInRegion(regions=[field.fieldBoundary()])], - [NeverExcessivelyDribbles()] + [NeverExcessivelyDribbles()], ] ag_always_validation_sequence_set = [[FriendlyAlwaysHasBallPossession()]] diff --git a/src/software/ai/hl/stp/tactic/dribble/BUILD b/src/software/ai/hl/stp/tactic/dribble/BUILD index 2351832276..67199a889f 100644 --- a/src/software/ai/hl/stp/tactic/dribble/BUILD +++ b/src/software/ai/hl/stp/tactic/dribble/BUILD @@ -79,4 +79,3 @@ py_test( requirement("pytest"), ], ) - diff --git a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py index 3fdb456b99..bc9b6fbb96 100644 --- a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py +++ b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py @@ -12,34 +12,35 @@ pytest_main, ) from proto.message_translation.tbots_protobuf import create_world_state -from proto.ssl_gc_common_pb2 import Team + @pytest.mark.parametrize( "initial_location,dribble_destination,final_dribble_orientation,allow_excessive_dribbling", [ # Dribble Destination for the ball < 1.0 from its starting position (tbots_cpp.Point(0.5, 0), tbots_cpp.Point(1.02, 0), tbots_cpp.Angle(), True), - # Dribble Testing diagonally - (tbots_cpp.Point(0.25, 0.25), tbots_cpp.Point(0.85, 0.65), tbots_cpp.Angle.fromRadians(50), True), - + ( + tbots_cpp.Point(0.25, 0.25), + tbots_cpp.Point(0.85, 0.65), + tbots_cpp.Angle.fromRadians(50), + True, + ), # Boundary Testing, because of the autoref implementation (initial of position Bot to final of Ball), # a dribble distance a tiny bit over 1m should pass (tbots_cpp.Point(0.01, 0), tbots_cpp.Point(1.025, 0), tbots_cpp.Angle(), True), (tbots_cpp.Point(1, 1.5), tbots_cpp.Point(2, 1.5), tbots_cpp.Angle(), True), - # When the robot is directly on the ball, the orientation at which the bot begins to # dribble is different; there appears to be a bit more leeway (tbots_cpp.Point(0, 1), tbots_cpp.Point(0, 2.025), tbots_cpp.Angle(), True), ], ) - def test_not_excessive_dribbling( - initial_location, - dribble_destination, - final_dribble_orientation, - allow_excessive_dribbling, - simulated_test_runner, + initial_location, + dribble_destination, + final_dribble_orientation, + allow_excessive_dribbling, + simulated_test_runner, ): simulated_test_runner.simulator_proto_unix_io.send_proto( WorldState, @@ -51,13 +52,14 @@ def test_not_excessive_dribbling( ), ) - # Setup Tactic params = AssignedTacticPlayControlParams() params.assigned_tactics[0].dribble.CopyFrom( DribbleTactic( dribble_destination=tbots_cpp.createPointProto(dribble_destination), - final_dribble_orientation=tbots_cpp.createAngleProto(final_dribble_orientation), + final_dribble_orientation=tbots_cpp.createAngleProto( + final_dribble_orientation + ), allow_excessive_dribbling=allow_excessive_dribbling, ) ) @@ -72,9 +74,7 @@ def test_not_excessive_dribbling( ) # Always Validation - always_validation_sequence_set = [ - [NeverExcessivelyDribbles()] - ] + always_validation_sequence_set = [[NeverExcessivelyDribbles()]] # Eventually Validation eventually_validation_sequence_set = [[]] @@ -93,11 +93,15 @@ def test_not_excessive_dribbling( # Dribble Destination for the ball > 1.0 from its starting position (tbots_cpp.Point(0, 2), tbots_cpp.Point(0, 0.5), tbots_cpp.Angle(), True), # Dribble Testing diagonally - (tbots_cpp.Point(0.1, 1.1), tbots_cpp.Point(1.1, 0.1), tbots_cpp.Angle.fromRadians(50), True), + ( + tbots_cpp.Point(0.1, 1.1), + tbots_cpp.Point(1.1, 0.1), + tbots_cpp.Angle.fromRadians(50), + True, + ), # Boundary Testing, because of the autoref implementation (initial position of Bot to final of Ball), # a dribble distance a tiny bit over 1m should pass, but too much (> 1.015 from testing) fails (tbots_cpp.Point(0, 1), tbots_cpp.Point(0, 2.02), tbots_cpp.Angle(), True), - # When the robot is directly on the ball, the orientation at which the bot begins to # dribble is different; there appears to be a bit more leeway on the distance that passes # Furthermore, that leeway appears to be different depending on the position of the bot and ball @@ -105,13 +109,12 @@ def test_not_excessive_dribbling( (tbots_cpp.Point(0, 0), tbots_cpp.Point(0, 1.035), tbots_cpp.Angle(), True), ], ) - def test_excessive_dribbling( - initial_location, - dribble_destination, - final_dribble_orientation, - allow_excessive_dribbling, - simulated_test_runner, + initial_location, + dribble_destination, + final_dribble_orientation, + allow_excessive_dribbling, + simulated_test_runner, ): simulated_test_runner.simulator_proto_unix_io.send_proto( WorldState, @@ -123,13 +126,14 @@ def test_excessive_dribbling( ), ) - # Setup Tactic params = AssignedTacticPlayControlParams() params.assigned_tactics[0].dribble.CopyFrom( DribbleTactic( dribble_destination=tbots_cpp.createPointProto(dribble_destination), - final_dribble_orientation=tbots_cpp.createAngleProto(final_dribble_orientation), + final_dribble_orientation=tbots_cpp.createAngleProto( + final_dribble_orientation + ), allow_excessive_dribbling=allow_excessive_dribbling, ) ) diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/excessive_dribbling.py index f7d50bd097..2f6eb31cae 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/excessive_dribbling.py @@ -14,10 +14,11 @@ class ExcessivelyDribbling(Validation): def __init__(self): self.continous_dribbling_start_point = None - - @override - def get_validation_status(self, world, max_dribble_length: float = 1.00) -> ValidationStatus: + @override + def get_validation_status( + self, world, max_dribble_length: float = 1.00 + ) -> ValidationStatus: """Checks if any friendly robot is excessively dribbling the ball, i.e. for over 1m. :param world: The world msg to validate From 5b0b98c3172926d7c6cf7ddeaea3b46a43a12a4d Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Sun, 30 Nov 2025 14:49:00 -0800 Subject: [PATCH 08/34] Added error margin of 0.05 to max dribbling distance --- .../tactic/dribble/excessive_dribble_test.py | 43 ++++++++++++------- .../simulated_tests/excessive_dribbling.py | 6 ++- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py index 3fdb456b99..98c4a70815 100644 --- a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py +++ b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py @@ -21,16 +21,19 @@ (tbots_cpp.Point(0.5, 0), tbots_cpp.Point(1.02, 0), tbots_cpp.Angle(), True), # Dribble Testing diagonally - (tbots_cpp.Point(0.25, 0.25), tbots_cpp.Point(0.85, 0.65), tbots_cpp.Angle.fromRadians(50), True), + (tbots_cpp.Point(0.25, 0.25), tbots_cpp.Point(0.80, 0.50), tbots_cpp.Angle.fromRadians(50), True), # Boundary Testing, because of the autoref implementation (initial of position Bot to final of Ball), - # a dribble distance a tiny bit over 1m should pass - (tbots_cpp.Point(0.01, 0), tbots_cpp.Point(1.025, 0), tbots_cpp.Angle(), True), - (tbots_cpp.Point(1, 1.5), tbots_cpp.Point(2, 1.5), tbots_cpp.Angle(), True), + # a conservative max dribble distance (0.95 m) is used - # When the robot is directly on the ball, the orientation at which the bot begins to - # dribble is different; there appears to be a bit more leeway - (tbots_cpp.Point(0, 1), tbots_cpp.Point(0, 2.025), tbots_cpp.Angle(), True), + # Test vertical dribbling + (tbots_cpp.Point(0.01, 0), tbots_cpp.Point(0.96, 0), tbots_cpp.Angle(), True), + + # Test horizontal dribbling + (tbots_cpp.Point(1, 1.5), tbots_cpp.Point(1.95, 1.5), tbots_cpp.Angle(), True), + + # Test bot and ball in same position + (tbots_cpp.Point(0, 1), tbots_cpp.Point(0.95, 1), tbots_cpp.Angle(), True), ], ) @@ -94,15 +97,23 @@ def test_not_excessive_dribbling( (tbots_cpp.Point(0, 2), tbots_cpp.Point(0, 0.5), tbots_cpp.Angle(), True), # Dribble Testing diagonally (tbots_cpp.Point(0.1, 1.1), tbots_cpp.Point(1.1, 0.1), tbots_cpp.Angle.fromRadians(50), True), - # Boundary Testing, because of the autoref implementation (initial position of Bot to final of Ball), - # a dribble distance a tiny bit over 1m should pass, but too much (> 1.015 from testing) fails - (tbots_cpp.Point(0, 1), tbots_cpp.Point(0, 2.02), tbots_cpp.Angle(), True), - - # When the robot is directly on the ball, the orientation at which the bot begins to - # dribble is different; there appears to be a bit more leeway on the distance that passes - # Furthermore, that leeway appears to be different depending on the position of the bot and ball - # (e.g. here the point is 0, 0, and the leeway is 1 cm more than at 0, 1) - (tbots_cpp.Point(0, 0), tbots_cpp.Point(0, 1.035), tbots_cpp.Angle(), True), + + # Boundary Testing, due to the conservative implementation a dribble distance of 1 m should fail + + # Test Vertical Dribbling + (tbots_cpp.Point(0, 1), tbots_cpp.Point(0, 2), tbots_cpp.Angle(), True), + + # Test Horizontal Dribbling + (tbots_cpp.Point(1, 2), tbots_cpp.Point(0, 2), tbots_cpp.Angle(), True), + + # Test Diagonal Dribbling + (tbots_cpp.Point(0, 1), tbots_cpp.Point(0.6, 1.8), tbots_cpp.Angle(), True), + + # Test robot and ball at same position (affects dribbling orientation and therefore perceived dribble distance) + (tbots_cpp.Point(0, 0), tbots_cpp.Point(0, 1), tbots_cpp.Angle(), True), + (tbots_cpp.Point(0.0, 0.01), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True), + (tbots_cpp.Point(0.01, 0.00), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True), + (tbots_cpp.Point(0.0, 0.00), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True), ], ) diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/excessive_dribbling.py index eaacd15eee..7b87d581cb 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/excessive_dribbling.py @@ -14,11 +14,13 @@ class ExcessivelyDribbling(Validation): def __init__(self): self.continous_dribbling_start_point = None - def get_validation_status(self, world, max_dribble_length: float = 1.00) -> ValidationStatus: + def get_validation_status(self, world, max_dribble_length: float = 1.00, max_dribble_error_margin: float = 0.05) -> ValidationStatus: """Checks if any friendly robot is excessively dribbling the ball, i.e. for over 1m. :param world: The world msg to validate :param max_dribble_length: the maximum dribble distance allowed (competition 1m) + :param max_dribble_error_margin: the error margin in the max dribble distance to allow for a conservative + estimate of max dribble distance (effective dribble distance is length - error margin) :return: FAILING when the robot is excessively dribbling PASSING when the robot is not excessively dribbling """ @@ -27,7 +29,7 @@ def get_validation_status(self, world, max_dribble_length: float = 1.00) -> Vali if world.HasField("dribble_displacement"): dribble_disp = world.dribble_displacement dist = tbots_cpp.createSegment(dribble_disp).length() - if dist > max_dribble_length: + if dist > (max_dribble_length - max_dribble_error_margin): return ValidationStatus.FAILING return ValidationStatus.PASSING From dff8853db5feaee2fc247d36431f1b21bdb52406 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sun, 30 Nov 2025 23:08:10 +0000 Subject: [PATCH 09/34] [pre-commit.ci lite] apply automatic fixes --- .../tactic/dribble/excessive_dribble_test.py | 83 +++++++++++-------- .../simulated_tests/excessive_dribbling.py | 7 +- 2 files changed, 54 insertions(+), 36 deletions(-) diff --git a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py index 98c4a70815..778f981816 100644 --- a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py +++ b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py @@ -12,37 +12,36 @@ pytest_main, ) from proto.message_translation.tbots_protobuf import create_world_state -from proto.ssl_gc_common_pb2 import Team + @pytest.mark.parametrize( "initial_location,dribble_destination,final_dribble_orientation,allow_excessive_dribbling", [ # Dribble Destination for the ball < 1.0 from its starting position (tbots_cpp.Point(0.5, 0), tbots_cpp.Point(1.02, 0), tbots_cpp.Angle(), True), - # Dribble Testing diagonally - (tbots_cpp.Point(0.25, 0.25), tbots_cpp.Point(0.80, 0.50), tbots_cpp.Angle.fromRadians(50), True), - + ( + tbots_cpp.Point(0.25, 0.25), + tbots_cpp.Point(0.80, 0.50), + tbots_cpp.Angle.fromRadians(50), + True, + ), # Boundary Testing, because of the autoref implementation (initial of position Bot to final of Ball), # a conservative max dribble distance (0.95 m) is used - # Test vertical dribbling (tbots_cpp.Point(0.01, 0), tbots_cpp.Point(0.96, 0), tbots_cpp.Angle(), True), - # Test horizontal dribbling (tbots_cpp.Point(1, 1.5), tbots_cpp.Point(1.95, 1.5), tbots_cpp.Angle(), True), - # Test bot and ball in same position (tbots_cpp.Point(0, 1), tbots_cpp.Point(0.95, 1), tbots_cpp.Angle(), True), ], ) - def test_not_excessive_dribbling( - initial_location, - dribble_destination, - final_dribble_orientation, - allow_excessive_dribbling, - simulated_test_runner, + initial_location, + dribble_destination, + final_dribble_orientation, + allow_excessive_dribbling, + simulated_test_runner, ): simulated_test_runner.simulator_proto_unix_io.send_proto( WorldState, @@ -54,13 +53,14 @@ def test_not_excessive_dribbling( ), ) - # Setup Tactic params = AssignedTacticPlayControlParams() params.assigned_tactics[0].dribble.CopyFrom( DribbleTactic( dribble_destination=tbots_cpp.createPointProto(dribble_destination), - final_dribble_orientation=tbots_cpp.createAngleProto(final_dribble_orientation), + final_dribble_orientation=tbots_cpp.createAngleProto( + final_dribble_orientation + ), allow_excessive_dribbling=allow_excessive_dribbling, ) ) @@ -75,9 +75,7 @@ def test_not_excessive_dribbling( ) # Always Validation - always_validation_sequence_set = [ - [NeverExcessivelyDribbles()] - ] + always_validation_sequence_set = [[NeverExcessivelyDribbles()]] # Eventually Validation eventually_validation_sequence_set = [[]] @@ -96,33 +94,47 @@ def test_not_excessive_dribbling( # Dribble Destination for the ball > 1.0 from its starting position (tbots_cpp.Point(0, 2), tbots_cpp.Point(0, 0.5), tbots_cpp.Angle(), True), # Dribble Testing diagonally - (tbots_cpp.Point(0.1, 1.1), tbots_cpp.Point(1.1, 0.1), tbots_cpp.Angle.fromRadians(50), True), - + ( + tbots_cpp.Point(0.1, 1.1), + tbots_cpp.Point(1.1, 0.1), + tbots_cpp.Angle.fromRadians(50), + True, + ), # Boundary Testing, due to the conservative implementation a dribble distance of 1 m should fail - # Test Vertical Dribbling (tbots_cpp.Point(0, 1), tbots_cpp.Point(0, 2), tbots_cpp.Angle(), True), - # Test Horizontal Dribbling (tbots_cpp.Point(1, 2), tbots_cpp.Point(0, 2), tbots_cpp.Angle(), True), - # Test Diagonal Dribbling (tbots_cpp.Point(0, 1), tbots_cpp.Point(0.6, 1.8), tbots_cpp.Angle(), True), - # Test robot and ball at same position (affects dribbling orientation and therefore perceived dribble distance) (tbots_cpp.Point(0, 0), tbots_cpp.Point(0, 1), tbots_cpp.Angle(), True), - (tbots_cpp.Point(0.0, 0.01), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True), - (tbots_cpp.Point(0.01, 0.00), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True), - (tbots_cpp.Point(0.0, 0.00), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True), + ( + tbots_cpp.Point(0.0, 0.01), + tbots_cpp.Point(0.81, 0.61), + tbots_cpp.Angle(), + True, + ), + ( + tbots_cpp.Point(0.01, 0.00), + tbots_cpp.Point(0.81, 0.61), + tbots_cpp.Angle(), + True, + ), + ( + tbots_cpp.Point(0.0, 0.00), + tbots_cpp.Point(0.81, 0.61), + tbots_cpp.Angle(), + True, + ), ], ) - def test_excessive_dribbling( - initial_location, - dribble_destination, - final_dribble_orientation, - allow_excessive_dribbling, - simulated_test_runner, + initial_location, + dribble_destination, + final_dribble_orientation, + allow_excessive_dribbling, + simulated_test_runner, ): simulated_test_runner.simulator_proto_unix_io.send_proto( WorldState, @@ -134,13 +146,14 @@ def test_excessive_dribbling( ), ) - # Setup Tactic params = AssignedTacticPlayControlParams() params.assigned_tactics[0].dribble.CopyFrom( DribbleTactic( dribble_destination=tbots_cpp.createPointProto(dribble_destination), - final_dribble_orientation=tbots_cpp.createAngleProto(final_dribble_orientation), + final_dribble_orientation=tbots_cpp.createAngleProto( + final_dribble_orientation + ), allow_excessive_dribbling=allow_excessive_dribbling, ) ) diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/excessive_dribbling.py index 7b87d581cb..2134916267 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/excessive_dribbling.py @@ -14,7 +14,12 @@ class ExcessivelyDribbling(Validation): def __init__(self): self.continous_dribbling_start_point = None - def get_validation_status(self, world, max_dribble_length: float = 1.00, max_dribble_error_margin: float = 0.05) -> ValidationStatus: + def get_validation_status( + self, + world, + max_dribble_length: float = 1.00, + max_dribble_error_margin: float = 0.05, + ) -> ValidationStatus: """Checks if any friendly robot is excessively dribbling the ball, i.e. for over 1m. :param world: The world msg to validate From 8336df98bc36aabc6ceb713bce95bd4abdbd106f Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Thu, 8 Jan 2026 22:59:18 -0800 Subject: [PATCH 10/34] Made testing file one function --- .../tactic/dribble/excessive_dribble_test.py | 117 ++++++------------ 1 file changed, 36 insertions(+), 81 deletions(-) diff --git a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py index 98c4a70815..087b16ab5e 100644 --- a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py +++ b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py @@ -15,105 +15,52 @@ from proto.ssl_gc_common_pb2 import Team @pytest.mark.parametrize( - "initial_location,dribble_destination,final_dribble_orientation,allow_excessive_dribbling", + "initial_location,dribble_destination,final_dribble_orientation,allow_excessive_dribbling, should_excessively_dribble", [ + # Tests Should not excessively dribble + # Dribble Destination for the ball < 1.0 from its starting position - (tbots_cpp.Point(0.5, 0), tbots_cpp.Point(1.02, 0), tbots_cpp.Angle(), True), + (tbots_cpp.Point(0.5, 0), tbots_cpp.Point(1.02, 0), tbots_cpp.Angle(), True, False), # Dribble Testing diagonally - (tbots_cpp.Point(0.25, 0.25), tbots_cpp.Point(0.80, 0.50), tbots_cpp.Angle.fromRadians(50), True), + (tbots_cpp.Point(0.25, 0.25), tbots_cpp.Point(0.80, 0.50), tbots_cpp.Angle.fromRadians(50), True, False), # Boundary Testing, because of the autoref implementation (initial of position Bot to final of Ball), # a conservative max dribble distance (0.95 m) is used # Test vertical dribbling - (tbots_cpp.Point(0.01, 0), tbots_cpp.Point(0.96, 0), tbots_cpp.Angle(), True), + (tbots_cpp.Point(0.01, 0), tbots_cpp.Point(0.96, 0), tbots_cpp.Angle(), True, False), # Test horizontal dribbling - (tbots_cpp.Point(1, 1.5), tbots_cpp.Point(1.95, 1.5), tbots_cpp.Angle(), True), + (tbots_cpp.Point(1, 1.5), tbots_cpp.Point(1.95, 1.5), tbots_cpp.Angle(), True, False), # Test bot and ball in same position - (tbots_cpp.Point(0, 1), tbots_cpp.Point(0.95, 1), tbots_cpp.Angle(), True), - ], -) - -def test_not_excessive_dribbling( - initial_location, - dribble_destination, - final_dribble_orientation, - allow_excessive_dribbling, - simulated_test_runner, -): - simulated_test_runner.simulator_proto_unix_io.send_proto( - WorldState, - create_world_state( - [], - blue_robot_locations=[tbots_cpp.Point(0, 1)], - ball_location=initial_location, - ball_velocity=tbots_cpp.Vector(0, 0), - ), - ) + (tbots_cpp.Point(0, 1), tbots_cpp.Point(0.95, 1), tbots_cpp.Angle(), True, False), + # Tests Should excessively dribble - # Setup Tactic - params = AssignedTacticPlayControlParams() - params.assigned_tactics[0].dribble.CopyFrom( - DribbleTactic( - dribble_destination=tbots_cpp.createPointProto(dribble_destination), - final_dribble_orientation=tbots_cpp.createAngleProto(final_dribble_orientation), - allow_excessive_dribbling=allow_excessive_dribbling, - ) - ) - simulated_test_runner.blue_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) - - # Setup no tactics on the enemy side - params = AssignedTacticPlayControlParams() - simulated_test_runner.yellow_full_system_proto_unix_io.send_proto( - AssignedTacticPlayControlParams, params - ) - - # Always Validation - always_validation_sequence_set = [ - [NeverExcessivelyDribbles()] - ] - - # Eventually Validation - eventually_validation_sequence_set = [[]] - - simulated_test_runner.run_test( - inv_eventually_validation_sequence_set=eventually_validation_sequence_set, - inv_always_validation_sequence_set=always_validation_sequence_set, - ag_eventually_validation_sequence_set=eventually_validation_sequence_set, - ag_always_validation_sequence_set=always_validation_sequence_set, - ) - - -@pytest.mark.parametrize( - "initial_location,dribble_destination,final_dribble_orientation,allow_excessive_dribbling", - [ # Dribble Destination for the ball > 1.0 from its starting position - (tbots_cpp.Point(0, 2), tbots_cpp.Point(0, 0.5), tbots_cpp.Angle(), True), + (tbots_cpp.Point(0, 2), tbots_cpp.Point(0, 0.5), tbots_cpp.Angle(), True, True), + # Dribble Testing diagonally - (tbots_cpp.Point(0.1, 1.1), tbots_cpp.Point(1.1, 0.1), tbots_cpp.Angle.fromRadians(50), True), + (tbots_cpp.Point(0.1, 1.1), tbots_cpp.Point(1.1, 0.1), tbots_cpp.Angle.fromRadians(50), True, True), # Boundary Testing, due to the conservative implementation a dribble distance of 1 m should fail # Test Vertical Dribbling - (tbots_cpp.Point(0, 1), tbots_cpp.Point(0, 2), tbots_cpp.Angle(), True), + (tbots_cpp.Point(0, 1), tbots_cpp.Point(0, 2), tbots_cpp.Angle(), True, True), # Test Horizontal Dribbling - (tbots_cpp.Point(1, 2), tbots_cpp.Point(0, 2), tbots_cpp.Angle(), True), + (tbots_cpp.Point(1, 2), tbots_cpp.Point(0, 2), tbots_cpp.Angle(), True, True), # Test Diagonal Dribbling - (tbots_cpp.Point(0, 1), tbots_cpp.Point(0.6, 1.8), tbots_cpp.Angle(), True), + (tbots_cpp.Point(0, 1), tbots_cpp.Point(0.6, 1.8), tbots_cpp.Angle(), True, True), # Test robot and ball at same position (affects dribbling orientation and therefore perceived dribble distance) - (tbots_cpp.Point(0, 0), tbots_cpp.Point(0, 1), tbots_cpp.Angle(), True), - (tbots_cpp.Point(0.0, 0.01), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True), - (tbots_cpp.Point(0.01, 0.00), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True), - (tbots_cpp.Point(0.0, 0.00), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True), + (tbots_cpp.Point(0, 0), tbots_cpp.Point(0, 1), tbots_cpp.Angle(), True, True), + (tbots_cpp.Point(0.0, 0.01), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True, True), + (tbots_cpp.Point(0.01, 0.00), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True, True), + (tbots_cpp.Point(0.0, 0.00), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True, True), ], ) @@ -122,19 +69,33 @@ def test_excessive_dribbling( dribble_destination, final_dribble_orientation, allow_excessive_dribbling, + should_excessively_dribble, simulated_test_runner, ): + if should_excessively_dribble: + blue_robot_locations = [tbots_cpp.Point(0, 0.0)] + + # Always and Eventually validation sets for excessive dribbling + always_validation_sequence_set = [[]] + eventually_validation_sequence_set = [[EventuallyStartsExcessivelyDribbling()]] + else: + blue_robot_locations = [tbots_cpp.Point(0,1)] + + # Always and Eventually validation sets for not excessive dribbling + always_validation_sequence_set = [[NeverExcessivelyDribbles()]] + eventually_validation_sequence_set = [[]] + + simulated_test_runner.simulator_proto_unix_io.send_proto( WorldState, create_world_state( [], - blue_robot_locations=[tbots_cpp.Point(0, 0.0)], + blue_robot_locations=blue_robot_locations, ball_location=initial_location, ball_velocity=tbots_cpp.Vector(0, 0), ), ) - # Setup Tactic params = AssignedTacticPlayControlParams() params.assigned_tactics[0].dribble.CopyFrom( @@ -144,6 +105,7 @@ def test_excessive_dribbling( allow_excessive_dribbling=allow_excessive_dribbling, ) ) + simulated_test_runner.blue_full_system_proto_unix_io.send_proto( AssignedTacticPlayControlParams, params ) @@ -154,12 +116,6 @@ def test_excessive_dribbling( AssignedTacticPlayControlParams, params ) - # Always Validation - always_validation_sequence_set = [[]] - - # Eventually Validation - eventually_validation_sequence_set = [[EventuallyStartsExcessivelyDribbling()]] - simulated_test_runner.run_test( inv_eventually_validation_sequence_set=eventually_validation_sequence_set, inv_always_validation_sequence_set=always_validation_sequence_set, @@ -167,6 +123,5 @@ def test_excessive_dribbling( ag_always_validation_sequence_set=always_validation_sequence_set, ) - if __name__ == "__main__": pytest_main(__file__) From 8d1354f1bfeaa5bca509580ba68b9b74c9056408 Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Thu, 8 Jan 2026 22:59:33 -0800 Subject: [PATCH 11/34] Increased max dribble margin to 0.06 to reduce flakiness --- src/software/simulated_tests/excessive_dribbling.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/excessive_dribbling.py index 7b87d581cb..8668453f9f 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/excessive_dribbling.py @@ -7,6 +7,7 @@ create_validation_types, ) +from typing import override class ExcessivelyDribbling(Validation): """Checks if any friendly robot is excessively dribbling the ball, i.e. for over 1m.""" @@ -14,7 +15,7 @@ class ExcessivelyDribbling(Validation): def __init__(self): self.continous_dribbling_start_point = None - def get_validation_status(self, world, max_dribble_length: float = 1.00, max_dribble_error_margin: float = 0.05) -> ValidationStatus: + def get_validation_status(self, world, max_dribble_length: float = 1.00, max_dribble_error_margin: float = 0.06) -> ValidationStatus: """Checks if any friendly robot is excessively dribbling the ball, i.e. for over 1m. :param world: The world msg to validate From 75141c31035d909c4c1602fcaf3714fdaa135404 Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Fri, 9 Jan 2026 00:39:35 -0800 Subject: [PATCH 12/34] Fixed import statements for test file --- .../ai/hl/stp/tactic/dribble/excessive_dribble_test.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py index 087b16ab5e..c5916b5168 100644 --- a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py +++ b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py @@ -1,13 +1,8 @@ import pytest import software.python_bindings as tbots_cpp -from software.simulated_tests.robot_enters_region import * -from software.simulated_tests.ball_enters_region import * -from software.simulated_tests.ball_moves_in_direction import * -from software.simulated_tests.friendly_has_ball_possession import * -from software.simulated_tests.ball_speed_threshold import * -from software.simulated_tests.robot_speed_threshold import * -from software.simulated_tests.excessive_dribbling import * +from software.simulated_tests.excessive_dribbling import NeverExcessivelyDribbles, EventuallyStartsExcessivelyDribbling +from proto.message_translation.tbots_protobuf import WorldState, AssignedTacticPlayControlParams, DribbleTactic from software.simulated_tests.simulated_test_fixture import ( pytest_main, ) @@ -60,7 +55,6 @@ (tbots_cpp.Point(0, 0), tbots_cpp.Point(0, 1), tbots_cpp.Angle(), True, True), (tbots_cpp.Point(0.0, 0.01), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True, True), (tbots_cpp.Point(0.01, 0.00), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True, True), - (tbots_cpp.Point(0.0, 0.00), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True, True), ], ) From 2d235e39f4a4de7ca629bced2dde97962730fde0 Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Fri, 9 Jan 2026 00:39:59 -0800 Subject: [PATCH 13/34] Changed get validation to reflect correct start point --- .../simulated_tests/excessive_dribbling.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/excessive_dribbling.py index 8668453f9f..852652a10d 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/excessive_dribbling.py @@ -1,5 +1,5 @@ import software.python_bindings as tbots_cpp -from proto.import_all_protos import * +from proto.import_all_protos import ValidationStatus, ValidationGeometry from software.simulated_tests.validation import ( Validation, @@ -15,7 +15,7 @@ class ExcessivelyDribbling(Validation): def __init__(self): self.continous_dribbling_start_point = None - def get_validation_status(self, world, max_dribble_length: float = 1.00, max_dribble_error_margin: float = 0.06) -> ValidationStatus: + def get_validation_status(self, world, max_dribble_length: float = 1.00, max_dribble_error_margin: float = 0.05) -> ValidationStatus: """Checks if any friendly robot is excessively dribbling the ball, i.e. for over 1m. :param world: The world msg to validate @@ -27,6 +27,7 @@ def get_validation_status(self, world, max_dribble_length: float = 1.00, max_dri """ # Use world calculation of dribbling distance, which uses implementation # of initial position of BOT to final position of BALL + if world.HasField("dribble_displacement"): dribble_disp = world.dribble_displacement dist = tbots_cpp.createSegment(dribble_disp).length() @@ -37,11 +38,13 @@ def get_validation_status(self, world, max_dribble_length: float = 1.00, max_dri def get_validation_geometry(self, world) -> ValidationGeometry: """(override) Shows the max allowed dribbling circle""" - return create_validation_geometry( - [tbots_cpp.Circle(self.continous_dribbling_start_point, 1.0)] - if self.continous_dribbling_start_point is not None - else [] - ) + if world.HasField("dribble_displacement"): + dribbling_start_point = tbots_cpp.createSegment(world.dribble_displacement).getStart() + return create_validation_geometry( + [tbots_cpp.Circle(dribbling_start_point, 1.0)] + ) + return create_validation_geometry([]) + def __repr__(self): return "Check that the dribbling robot has not dribbled for more than 1m" From e3afcc59e91f6f411338876bbd37f0f686ff8353 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 09:08:22 +0000 Subject: [PATCH 14/34] [pre-commit.ci lite] apply automatic fixes --- .../tactic/dribble/excessive_dribble_test.py | 120 ++++++++++++------ .../simulated_tests/excessive_dribbling.py | 6 +- 2 files changed, 87 insertions(+), 39 deletions(-) diff --git a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py index c5916b5168..a7eba426ee 100644 --- a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py +++ b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py @@ -1,70 +1,116 @@ import pytest import software.python_bindings as tbots_cpp -from software.simulated_tests.excessive_dribbling import NeverExcessivelyDribbles, EventuallyStartsExcessivelyDribbling -from proto.message_translation.tbots_protobuf import WorldState, AssignedTacticPlayControlParams, DribbleTactic +from software.simulated_tests.excessive_dribbling import ( + NeverExcessivelyDribbles, + EventuallyStartsExcessivelyDribbling, +) +from proto.message_translation.tbots_protobuf import ( + WorldState, + AssignedTacticPlayControlParams, + DribbleTactic, +) from software.simulated_tests.simulated_test_fixture import ( pytest_main, ) from proto.message_translation.tbots_protobuf import create_world_state -from proto.ssl_gc_common_pb2 import Team + @pytest.mark.parametrize( "initial_location,dribble_destination,final_dribble_orientation,allow_excessive_dribbling, should_excessively_dribble", [ # Tests Should not excessively dribble - # Dribble Destination for the ball < 1.0 from its starting position - (tbots_cpp.Point(0.5, 0), tbots_cpp.Point(1.02, 0), tbots_cpp.Angle(), True, False), - + ( + tbots_cpp.Point(0.5, 0), + tbots_cpp.Point(1.02, 0), + tbots_cpp.Angle(), + True, + False, + ), # Dribble Testing diagonally - (tbots_cpp.Point(0.25, 0.25), tbots_cpp.Point(0.80, 0.50), tbots_cpp.Angle.fromRadians(50), True, False), - + ( + tbots_cpp.Point(0.25, 0.25), + tbots_cpp.Point(0.80, 0.50), + tbots_cpp.Angle.fromRadians(50), + True, + False, + ), # Boundary Testing, because of the autoref implementation (initial of position Bot to final of Ball), # a conservative max dribble distance (0.95 m) is used - # Test vertical dribbling - (tbots_cpp.Point(0.01, 0), tbots_cpp.Point(0.96, 0), tbots_cpp.Angle(), True, False), - + ( + tbots_cpp.Point(0.01, 0), + tbots_cpp.Point(0.96, 0), + tbots_cpp.Angle(), + True, + False, + ), # Test horizontal dribbling - (tbots_cpp.Point(1, 1.5), tbots_cpp.Point(1.95, 1.5), tbots_cpp.Angle(), True, False), - + ( + tbots_cpp.Point(1, 1.5), + tbots_cpp.Point(1.95, 1.5), + tbots_cpp.Angle(), + True, + False, + ), # Test bot and ball in same position - (tbots_cpp.Point(0, 1), tbots_cpp.Point(0.95, 1), tbots_cpp.Angle(), True, False), - + ( + tbots_cpp.Point(0, 1), + tbots_cpp.Point(0.95, 1), + tbots_cpp.Angle(), + True, + False, + ), # Tests Should excessively dribble - # Dribble Destination for the ball > 1.0 from its starting position (tbots_cpp.Point(0, 2), tbots_cpp.Point(0, 0.5), tbots_cpp.Angle(), True, True), - # Dribble Testing diagonally - (tbots_cpp.Point(0.1, 1.1), tbots_cpp.Point(1.1, 0.1), tbots_cpp.Angle.fromRadians(50), True, True), - + ( + tbots_cpp.Point(0.1, 1.1), + tbots_cpp.Point(1.1, 0.1), + tbots_cpp.Angle.fromRadians(50), + True, + True, + ), # Boundary Testing, due to the conservative implementation a dribble distance of 1 m should fail - # Test Vertical Dribbling (tbots_cpp.Point(0, 1), tbots_cpp.Point(0, 2), tbots_cpp.Angle(), True, True), - # Test Horizontal Dribbling (tbots_cpp.Point(1, 2), tbots_cpp.Point(0, 2), tbots_cpp.Angle(), True, True), - # Test Diagonal Dribbling - (tbots_cpp.Point(0, 1), tbots_cpp.Point(0.6, 1.8), tbots_cpp.Angle(), True, True), - + ( + tbots_cpp.Point(0, 1), + tbots_cpp.Point(0.6, 1.8), + tbots_cpp.Angle(), + True, + True, + ), # Test robot and ball at same position (affects dribbling orientation and therefore perceived dribble distance) (tbots_cpp.Point(0, 0), tbots_cpp.Point(0, 1), tbots_cpp.Angle(), True, True), - (tbots_cpp.Point(0.0, 0.01), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True, True), - (tbots_cpp.Point(0.01, 0.00), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True, True), + ( + tbots_cpp.Point(0.0, 0.01), + tbots_cpp.Point(0.81, 0.61), + tbots_cpp.Angle(), + True, + True, + ), + ( + tbots_cpp.Point(0.01, 0.00), + tbots_cpp.Point(0.81, 0.61), + tbots_cpp.Angle(), + True, + True, + ), ], ) - def test_excessive_dribbling( - initial_location, - dribble_destination, - final_dribble_orientation, - allow_excessive_dribbling, - should_excessively_dribble, - simulated_test_runner, + initial_location, + dribble_destination, + final_dribble_orientation, + allow_excessive_dribbling, + should_excessively_dribble, + simulated_test_runner, ): if should_excessively_dribble: blue_robot_locations = [tbots_cpp.Point(0, 0.0)] @@ -73,13 +119,12 @@ def test_excessive_dribbling( always_validation_sequence_set = [[]] eventually_validation_sequence_set = [[EventuallyStartsExcessivelyDribbling()]] else: - blue_robot_locations = [tbots_cpp.Point(0,1)] + blue_robot_locations = [tbots_cpp.Point(0, 1)] # Always and Eventually validation sets for not excessive dribbling always_validation_sequence_set = [[NeverExcessivelyDribbles()]] eventually_validation_sequence_set = [[]] - simulated_test_runner.simulator_proto_unix_io.send_proto( WorldState, create_world_state( @@ -95,7 +140,9 @@ def test_excessive_dribbling( params.assigned_tactics[0].dribble.CopyFrom( DribbleTactic( dribble_destination=tbots_cpp.createPointProto(dribble_destination), - final_dribble_orientation=tbots_cpp.createAngleProto(final_dribble_orientation), + final_dribble_orientation=tbots_cpp.createAngleProto( + final_dribble_orientation + ), allow_excessive_dribbling=allow_excessive_dribbling, ) ) @@ -117,5 +164,6 @@ def test_excessive_dribbling( ag_always_validation_sequence_set=always_validation_sequence_set, ) + if __name__ == "__main__": pytest_main(__file__) diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/excessive_dribbling.py index d5040e5c4d..0028dc49f7 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/excessive_dribbling.py @@ -7,7 +7,6 @@ create_validation_types, ) -from typing import override class ExcessivelyDribbling(Validation): """Checks if any friendly robot is excessively dribbling the ball, i.e. for over 1m.""" @@ -44,13 +43,14 @@ def get_validation_status( def get_validation_geometry(self, world) -> ValidationGeometry: """(override) Shows the max allowed dribbling circle""" if world.HasField("dribble_displacement"): - dribbling_start_point = tbots_cpp.createSegment(world.dribble_displacement).getStart() + dribbling_start_point = tbots_cpp.createSegment( + world.dribble_displacement + ).getStart() return create_validation_geometry( [tbots_cpp.Circle(dribbling_start_point, 1.0)] ) return create_validation_geometry([]) - def __repr__(self): return "Check that the dribbling robot has not dribbled for more than 1m" From 7edc0738219c0f5ca4b41d7812240f5caf0dee45 Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Sat, 10 Jan 2026 12:22:57 -0800 Subject: [PATCH 15/34] Added constants to the excessive dribbling --- src/software/simulated_tests/excessive_dribbling.py | 10 ++-------- src/software/thunderscope/constants.py | 6 ++++++ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/excessive_dribbling.py index d5040e5c4d..3b6863c67c 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/excessive_dribbling.py @@ -1,5 +1,6 @@ import software.python_bindings as tbots_cpp from proto.import_all_protos import ValidationStatus, ValidationGeometry +from software.thunderscope.constants import DribblingConstants from software.simulated_tests.validation import ( Validation, @@ -12,20 +13,13 @@ class ExcessivelyDribbling(Validation): """Checks if any friendly robot is excessively dribbling the ball, i.e. for over 1m.""" - def __init__(self): - self.continous_dribbling_start_point = None - def get_validation_status( self, world, - max_dribble_length: float = 1.00, - max_dribble_error_margin: float = 0.05, ) -> ValidationStatus: """Checks if any friendly robot is excessively dribbling the ball, i.e. for over 1m. :param world: The world msg to validate - :param max_dribble_length: the maximum dribble distance allowed (competition 1m) - :param max_dribble_error_margin: the error margin in the max dribble distance to allow for a conservative estimate of max dribble distance (effective dribble distance is length - error margin) :return: FAILING when the robot is excessively dribbling PASSING when the robot is not excessively dribbling @@ -36,7 +30,7 @@ def get_validation_status( if world.HasField("dribble_displacement"): dribble_disp = world.dribble_displacement dist = tbots_cpp.createSegment(dribble_disp).length() - if dist > (max_dribble_length - max_dribble_error_margin): + if dist > (DribblingConstants.MAX_DRIBBLING_DISTANCE - DribblingConstants.DRIBBLING_ERROR_MARGIN): return ValidationStatus.FAILING return ValidationStatus.PASSING diff --git a/src/software/thunderscope/constants.py b/src/software/thunderscope/constants.py index e86f2582e5..11abe8bb8a 100644 --- a/src/software/thunderscope/constants.py +++ b/src/software/thunderscope/constants.py @@ -377,3 +377,9 @@ class ProtoPlayerFlags(Enum): NO_ERROR_FLAG = 0 UNCAUGHT_EXCEPTION_FLAG = 1 << 0 + +class DribblingConstants: + """Constants to be used to check whether the robot has dribbled beyond competition regulations""" + + MAX_DRIBBLING_DISTANCE = 1.0 + DRIBBLING_ERROR_MARGIN = 0.06 From 87eb7b948c296480a86fb1ae3f5b7bdb38f574c2 Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Sat, 10 Jan 2026 12:45:39 -0800 Subject: [PATCH 16/34] Removed allow excessive dribbling for the tests --- .../tactic/dribble/excessive_dribble_test.py | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py index c5916b5168..e73cf8e4e9 100644 --- a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py +++ b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py @@ -10,51 +10,51 @@ from proto.ssl_gc_common_pb2 import Team @pytest.mark.parametrize( - "initial_location,dribble_destination,final_dribble_orientation,allow_excessive_dribbling, should_excessively_dribble", + "initial_location,dribble_destination,final_dribble_orientation, should_excessively_dribble", [ # Tests Should not excessively dribble # Dribble Destination for the ball < 1.0 from its starting position - (tbots_cpp.Point(0.5, 0), tbots_cpp.Point(1.02, 0), tbots_cpp.Angle(), True, False), + (tbots_cpp.Point(0.5, 0), tbots_cpp.Point(1.02, 0), tbots_cpp.Angle(), False), # Dribble Testing diagonally - (tbots_cpp.Point(0.25, 0.25), tbots_cpp.Point(0.80, 0.50), tbots_cpp.Angle.fromRadians(50), True, False), + (tbots_cpp.Point(0.25, 0.25), tbots_cpp.Point(0.80, 0.50), tbots_cpp.Angle.fromRadians(50), False), # Boundary Testing, because of the autoref implementation (initial of position Bot to final of Ball), # a conservative max dribble distance (0.95 m) is used # Test vertical dribbling - (tbots_cpp.Point(0.01, 0), tbots_cpp.Point(0.96, 0), tbots_cpp.Angle(), True, False), + (tbots_cpp.Point(0.01, 0), tbots_cpp.Point(0.96, 0), tbots_cpp.Angle(), False), # Test horizontal dribbling - (tbots_cpp.Point(1, 1.5), tbots_cpp.Point(1.95, 1.5), tbots_cpp.Angle(), True, False), + (tbots_cpp.Point(1, 1.5), tbots_cpp.Point(1.95, 1.5), tbots_cpp.Angle(), False), # Test bot and ball in same position - (tbots_cpp.Point(0, 1), tbots_cpp.Point(0.95, 1), tbots_cpp.Angle(), True, False), + (tbots_cpp.Point(0, 1), tbots_cpp.Point(0.95, 1), tbots_cpp.Angle(), False), # Tests Should excessively dribble # Dribble Destination for the ball > 1.0 from its starting position - (tbots_cpp.Point(0, 2), tbots_cpp.Point(0, 0.5), tbots_cpp.Angle(), True, True), + (tbots_cpp.Point(0, 2), tbots_cpp.Point(0, 0.5), tbots_cpp.Angle(), True), # Dribble Testing diagonally - (tbots_cpp.Point(0.1, 1.1), tbots_cpp.Point(1.1, 0.1), tbots_cpp.Angle.fromRadians(50), True, True), + (tbots_cpp.Point(0.1, 1.1), tbots_cpp.Point(1.1, 0.1), tbots_cpp.Angle.fromRadians(50), True), # Boundary Testing, due to the conservative implementation a dribble distance of 1 m should fail # Test Vertical Dribbling - (tbots_cpp.Point(0, 1), tbots_cpp.Point(0, 2), tbots_cpp.Angle(), True, True), + (tbots_cpp.Point(0, 1), tbots_cpp.Point(0, 2), tbots_cpp.Angle(), True), # Test Horizontal Dribbling - (tbots_cpp.Point(1, 2), tbots_cpp.Point(0, 2), tbots_cpp.Angle(), True, True), + (tbots_cpp.Point(1, 2), tbots_cpp.Point(0, 2), tbots_cpp.Angle(), True), # Test Diagonal Dribbling - (tbots_cpp.Point(0, 1), tbots_cpp.Point(0.6, 1.8), tbots_cpp.Angle(), True, True), + (tbots_cpp.Point(0, 1), tbots_cpp.Point(0.6, 1.8), tbots_cpp.Angle(), True), # Test robot and ball at same position (affects dribbling orientation and therefore perceived dribble distance) - (tbots_cpp.Point(0, 0), tbots_cpp.Point(0, 1), tbots_cpp.Angle(), True, True), - (tbots_cpp.Point(0.0, 0.01), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True, True), - (tbots_cpp.Point(0.01, 0.00), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True, True), + (tbots_cpp.Point(0, 0), tbots_cpp.Point(0, 1), tbots_cpp.Angle(), True), + (tbots_cpp.Point(0.0, 0.01), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True), + (tbots_cpp.Point(0.01, 0.00), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True), ], ) @@ -62,7 +62,6 @@ def test_excessive_dribbling( initial_location, dribble_destination, final_dribble_orientation, - allow_excessive_dribbling, should_excessively_dribble, simulated_test_runner, ): @@ -96,7 +95,7 @@ def test_excessive_dribbling( DribbleTactic( dribble_destination=tbots_cpp.createPointProto(dribble_destination), final_dribble_orientation=tbots_cpp.createAngleProto(final_dribble_orientation), - allow_excessive_dribbling=allow_excessive_dribbling, + allow_excessive_dribbling=True, ) ) From 0e8e809e8277197161858117036f8a2cf35f072a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 10 Jan 2026 20:59:15 +0000 Subject: [PATCH 17/34] [pre-commit.ci lite] apply automatic fixes --- .../tactic/dribble/excessive_dribble_test.py | 74 +++++++++++-------- .../simulated_tests/excessive_dribbling.py | 5 +- src/software/thunderscope/constants.py | 1 + 3 files changed, 49 insertions(+), 31 deletions(-) diff --git a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py index e73cf8e4e9..6589e322d9 100644 --- a/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py +++ b/src/software/ai/hl/stp/tactic/dribble/excessive_dribble_test.py @@ -1,69 +1,81 @@ import pytest import software.python_bindings as tbots_cpp -from software.simulated_tests.excessive_dribbling import NeverExcessivelyDribbles, EventuallyStartsExcessivelyDribbling -from proto.message_translation.tbots_protobuf import WorldState, AssignedTacticPlayControlParams, DribbleTactic +from software.simulated_tests.excessive_dribbling import ( + NeverExcessivelyDribbles, + EventuallyStartsExcessivelyDribbling, +) +from proto.message_translation.tbots_protobuf import ( + WorldState, + AssignedTacticPlayControlParams, + DribbleTactic, +) from software.simulated_tests.simulated_test_fixture import ( pytest_main, ) from proto.message_translation.tbots_protobuf import create_world_state -from proto.ssl_gc_common_pb2 import Team + @pytest.mark.parametrize( "initial_location,dribble_destination,final_dribble_orientation, should_excessively_dribble", [ # Tests Should not excessively dribble - # Dribble Destination for the ball < 1.0 from its starting position (tbots_cpp.Point(0.5, 0), tbots_cpp.Point(1.02, 0), tbots_cpp.Angle(), False), - # Dribble Testing diagonally - (tbots_cpp.Point(0.25, 0.25), tbots_cpp.Point(0.80, 0.50), tbots_cpp.Angle.fromRadians(50), False), - + ( + tbots_cpp.Point(0.25, 0.25), + tbots_cpp.Point(0.80, 0.50), + tbots_cpp.Angle.fromRadians(50), + False, + ), # Boundary Testing, because of the autoref implementation (initial of position Bot to final of Ball), # a conservative max dribble distance (0.95 m) is used - # Test vertical dribbling (tbots_cpp.Point(0.01, 0), tbots_cpp.Point(0.96, 0), tbots_cpp.Angle(), False), - # Test horizontal dribbling (tbots_cpp.Point(1, 1.5), tbots_cpp.Point(1.95, 1.5), tbots_cpp.Angle(), False), - # Test bot and ball in same position (tbots_cpp.Point(0, 1), tbots_cpp.Point(0.95, 1), tbots_cpp.Angle(), False), - # Tests Should excessively dribble - # Dribble Destination for the ball > 1.0 from its starting position (tbots_cpp.Point(0, 2), tbots_cpp.Point(0, 0.5), tbots_cpp.Angle(), True), - # Dribble Testing diagonally - (tbots_cpp.Point(0.1, 1.1), tbots_cpp.Point(1.1, 0.1), tbots_cpp.Angle.fromRadians(50), True), - + ( + tbots_cpp.Point(0.1, 1.1), + tbots_cpp.Point(1.1, 0.1), + tbots_cpp.Angle.fromRadians(50), + True, + ), # Boundary Testing, due to the conservative implementation a dribble distance of 1 m should fail - # Test Vertical Dribbling (tbots_cpp.Point(0, 1), tbots_cpp.Point(0, 2), tbots_cpp.Angle(), True), - # Test Horizontal Dribbling (tbots_cpp.Point(1, 2), tbots_cpp.Point(0, 2), tbots_cpp.Angle(), True), - # Test Diagonal Dribbling (tbots_cpp.Point(0, 1), tbots_cpp.Point(0.6, 1.8), tbots_cpp.Angle(), True), - # Test robot and ball at same position (affects dribbling orientation and therefore perceived dribble distance) (tbots_cpp.Point(0, 0), tbots_cpp.Point(0, 1), tbots_cpp.Angle(), True), - (tbots_cpp.Point(0.0, 0.01), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True), - (tbots_cpp.Point(0.01, 0.00), tbots_cpp.Point(0.81, 0.61), tbots_cpp.Angle(), True), + ( + tbots_cpp.Point(0.0, 0.01), + tbots_cpp.Point(0.81, 0.61), + tbots_cpp.Angle(), + True, + ), + ( + tbots_cpp.Point(0.01, 0.00), + tbots_cpp.Point(0.81, 0.61), + tbots_cpp.Angle(), + True, + ), ], ) - def test_excessive_dribbling( - initial_location, - dribble_destination, - final_dribble_orientation, - should_excessively_dribble, - simulated_test_runner, + initial_location, + dribble_destination, + final_dribble_orientation, + should_excessively_dribble, + simulated_test_runner, ): if should_excessively_dribble: blue_robot_locations = [tbots_cpp.Point(0, 0.0)] @@ -72,13 +84,12 @@ def test_excessive_dribbling( always_validation_sequence_set = [[]] eventually_validation_sequence_set = [[EventuallyStartsExcessivelyDribbling()]] else: - blue_robot_locations = [tbots_cpp.Point(0,1)] + blue_robot_locations = [tbots_cpp.Point(0, 1)] # Always and Eventually validation sets for not excessive dribbling always_validation_sequence_set = [[NeverExcessivelyDribbles()]] eventually_validation_sequence_set = [[]] - simulated_test_runner.simulator_proto_unix_io.send_proto( WorldState, create_world_state( @@ -94,7 +105,9 @@ def test_excessive_dribbling( params.assigned_tactics[0].dribble.CopyFrom( DribbleTactic( dribble_destination=tbots_cpp.createPointProto(dribble_destination), - final_dribble_orientation=tbots_cpp.createAngleProto(final_dribble_orientation), + final_dribble_orientation=tbots_cpp.createAngleProto( + final_dribble_orientation + ), allow_excessive_dribbling=True, ) ) @@ -116,5 +129,6 @@ def test_excessive_dribbling( ag_always_validation_sequence_set=always_validation_sequence_set, ) + if __name__ == "__main__": pytest_main(__file__) diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/excessive_dribbling.py index 0ae49078b6..18eccb65ef 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/excessive_dribbling.py @@ -29,7 +29,10 @@ def get_validation_status( if world.HasField("dribble_displacement"): dribble_disp = world.dribble_displacement dist = tbots_cpp.createSegment(dribble_disp).length() - if dist > (DribblingConstants.MAX_DRIBBLING_DISTANCE - DribblingConstants.DRIBBLING_ERROR_MARGIN): + if dist > ( + DribblingConstants.MAX_DRIBBLING_DISTANCE + - DribblingConstants.DRIBBLING_ERROR_MARGIN + ): return ValidationStatus.FAILING return ValidationStatus.PASSING diff --git a/src/software/thunderscope/constants.py b/src/software/thunderscope/constants.py index 11abe8bb8a..10cf4ed92f 100644 --- a/src/software/thunderscope/constants.py +++ b/src/software/thunderscope/constants.py @@ -378,6 +378,7 @@ class ProtoPlayerFlags(Enum): NO_ERROR_FLAG = 0 UNCAUGHT_EXCEPTION_FLAG = 1 << 0 + class DribblingConstants: """Constants to be used to check whether the robot has dribbled beyond competition regulations""" From da9842744e53b1d4d8187332cbd71414da836f6c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 22:47:26 +0000 Subject: [PATCH 18/34] [pre-commit.ci lite] apply automatic fixes --- src/software/thunderscope/constants.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/software/thunderscope/constants.py b/src/software/thunderscope/constants.py index 8959344114..12cb5b0d83 100644 --- a/src/software/thunderscope/constants.py +++ b/src/software/thunderscope/constants.py @@ -384,8 +384,8 @@ class DribblingConstants: MAX_DRIBBLING_DISTANCE = 1.0 DRIBBLING_ERROR_MARGIN = 0.06 - - + + class LogLevels(StrEnum): """Log levels for FullSystem to indicate minimum logged level""" From 0916bcb553432aa27af5e90c43443dab5b53deb0 Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Sat, 31 Jan 2026 11:47:34 -0800 Subject: [PATCH 19/34] Added back import --- src/software/simulated_tests/excessive_dribbling.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/excessive_dribbling.py index 18eccb65ef..f6e49655b8 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/excessive_dribbling.py @@ -7,6 +7,7 @@ create_validation_geometry, create_validation_types, ) +from typing import override class ExcessivelyDribbling(Validation): @@ -34,6 +35,7 @@ def get_validation_status( - DribblingConstants.DRIBBLING_ERROR_MARGIN ): return ValidationStatus.FAILING + else: return ValidationStatus.PASSING From 82532bd5ed4955b594457cee5c47f5151119f60c Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Sat, 31 Jan 2026 11:50:34 -0800 Subject: [PATCH 20/34] Added back import --- src/software/simulated_tests/excessive_dribbling.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/excessive_dribbling.py index f6e49655b8..84d259d6aa 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/excessive_dribbling.py @@ -27,7 +27,7 @@ def get_validation_status( # Use world calculation of dribbling distance, which uses implementation # of initial position of BOT to final position of BALL - if world.HasField("dribble_displacement"): + if world.hasValue("dribble_displacement"): dribble_disp = world.dribble_displacement dist = tbots_cpp.createSegment(dribble_disp).length() if dist > ( @@ -35,7 +35,6 @@ def get_validation_status( - DribblingConstants.DRIBBLING_ERROR_MARGIN ): return ValidationStatus.FAILING - else: return ValidationStatus.PASSING From 6d9a3638a9475d73e2990f407ca6471b52ed5497 Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Sat, 31 Jan 2026 11:55:49 -0800 Subject: [PATCH 21/34] Added overrides --- src/software/simulated_tests/excessive_dribbling.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/excessive_dribbling.py index 84d259d6aa..85f7d75908 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/excessive_dribbling.py @@ -13,6 +13,7 @@ class ExcessivelyDribbling(Validation): """Checks if any friendly robot is excessively dribbling the ball, i.e. for over 1m.""" + @override def get_validation_status( self, world, @@ -38,6 +39,7 @@ def get_validation_status( return ValidationStatus.PASSING + @override def get_validation_geometry(self, world) -> ValidationGeometry: """(override) Shows the max allowed dribbling circle""" if world.HasField("dribble_displacement"): @@ -49,6 +51,7 @@ def get_validation_geometry(self, world) -> ValidationGeometry: ) return create_validation_geometry([]) + @override def __repr__(self): return "Check that the dribbling robot has not dribbled for more than 1m" From 871d21055f0c654d3db648099033f878daf751da Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 7 Feb 2026 19:22:29 +0000 Subject: [PATCH 22/34] [pre-commit.ci lite] apply automatic fixes --- .../thunderscope/requirements_lock.txt | 135 ------------------ 1 file changed, 135 deletions(-) diff --git a/src/software/thunderscope/requirements_lock.txt b/src/software/thunderscope/requirements_lock.txt index 1b4d0632ee..e69de29bb2 100644 --- a/src/software/thunderscope/requirements_lock.txt +++ b/src/software/thunderscope/requirements_lock.txt @@ -1,135 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# bazel run //software/thunderscope:requirements.update -# -colorama==0.4.6 \ - --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ - --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 - # via -r software/thunderscope/requirements.in -darkdetect==0.7.1 \ - --hash=sha256:3efe69f8ecd5f1b7f4fbb0d1d93f656b0e493c45cc49222380ffe2a529cbc866 \ - --hash=sha256:47be3cf5134432ddb616bbffc927237718407914993c82809983e7ccebf49013 - # via pyqtdarktheme-fork -evdev==1.7.0 ; sys_platform == "linux" \ - --hash=sha256:95bd2a1e0c6ce2cd7a2ecc6e6cd9736ff794b3ad5cb54d81d8cbc2e414d0b870 - # via -r software/thunderscope/requirements.in -netifaces==0.11.0 \ - --hash=sha256:043a79146eb2907edf439899f262b3dfe41717d34124298ed281139a8b93ca32 \ - --hash=sha256:08e3f102a59f9eaef70948340aeb6c89bd09734e0dca0f3b82720305729f63ea \ - --hash=sha256:0f6133ac02521270d9f7c490f0c8c60638ff4aec8338efeff10a1b51506abe85 \ - --hash=sha256:18917fbbdcb2d4f897153c5ddbb56b31fa6dd7c3fa9608b7e3c3a663df8206b5 \ - --hash=sha256:2479bb4bb50968089a7c045f24d120f37026d7e802ec134c4490eae994c729b5 \ - --hash=sha256:2650beee182fed66617e18474b943e72e52f10a24dc8cac1db36c41ee9c041b7 \ - --hash=sha256:28f4bf3a1361ab3ed93c5ef360c8b7d4a4ae060176a3529e72e5e4ffc4afd8b0 \ - --hash=sha256:3ecb3f37c31d5d51d2a4d935cfa81c9bc956687c6f5237021b36d6fdc2815b2c \ - --hash=sha256:469fc61034f3daf095e02f9f1bbac07927b826c76b745207287bc594884cfd05 \ - --hash=sha256:48324183af7f1bc44f5f197f3dad54a809ad1ef0c78baee2c88f16a5de02c4c9 \ - --hash=sha256:50721858c935a76b83dd0dd1ab472cad0a3ef540a1408057624604002fcfb45b \ - --hash=sha256:54ff6624eb95b8a07e79aa8817288659af174e954cca24cdb0daeeddfc03c4ff \ - --hash=sha256:5be83986100ed1fdfa78f11ccff9e4757297735ac17391b95e17e74335c2047d \ - --hash=sha256:5f9ca13babe4d845e400921973f6165a4c2f9f3379c7abfc7478160e25d196a4 \ - --hash=sha256:73ff21559675150d31deea8f1f8d7e9a9a7e4688732a94d71327082f517fc6b4 \ - --hash=sha256:7dbb71ea26d304e78ccccf6faccef71bb27ea35e259fb883cfd7fd7b4f17ecb1 \ - --hash=sha256:815eafdf8b8f2e61370afc6add6194bd5a7252ae44c667e96c4c1ecf418811e4 \ - --hash=sha256:841aa21110a20dc1621e3dd9f922c64ca64dd1eb213c47267a2c324d823f6c8f \ - --hash=sha256:84e4d2e6973eccc52778735befc01638498781ce0e39aa2044ccfd2385c03246 \ - --hash=sha256:8f7da24eab0d4184715d96208b38d373fd15c37b0dafb74756c638bd619ba150 \ - --hash=sha256:96c0fe9696398253f93482c84814f0e7290eee0bfec11563bd07d80d701280c3 \ - --hash=sha256:aab1dbfdc55086c789f0eb37affccf47b895b98d490738b81f3b2360100426be \ - --hash=sha256:c03fb2d4ef4e393f2e6ffc6376410a22a3544f164b336b3a355226653e5efd89 \ - --hash=sha256:c37a1ca83825bc6f54dddf5277e9c65dec2f1b4d0ba44b8fd42bc30c91aa6ea1 \ - --hash=sha256:c92ff9ac7c2282009fe0dcb67ee3cd17978cffbe0c8f4b471c00fe4325c9b4d4 \ - --hash=sha256:c9a3a47cd3aaeb71e93e681d9816c56406ed755b9442e981b07e3618fb71d2ac \ - --hash=sha256:cb925e1ca024d6f9b4f9b01d83215fd00fe69d095d0255ff3f64bffda74025c8 \ - --hash=sha256:d07b01c51b0b6ceb0f09fc48ec58debd99d2c8430b09e56651addeaf5de48048 \ - --hash=sha256:e76c7f351e0444721e85f975ae92718e21c1f361bda946d60a214061de1f00a1 \ - --hash=sha256:eb4813b77d5df99903af4757ce980a98c4d702bbcb81f32a0b305a1537bdf0b1 - # via -r software/thunderscope/requirements.in -numpy==1.26.4 \ - --hash=sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b \ - --hash=sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818 \ - --hash=sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20 \ - --hash=sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0 \ - --hash=sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010 \ - --hash=sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a \ - --hash=sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea \ - --hash=sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c \ - --hash=sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71 \ - --hash=sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110 \ - --hash=sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be \ - --hash=sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a \ - --hash=sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a \ - --hash=sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5 \ - --hash=sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed \ - --hash=sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd \ - --hash=sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c \ - --hash=sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e \ - --hash=sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0 \ - --hash=sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c \ - --hash=sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a \ - --hash=sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b \ - --hash=sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0 \ - --hash=sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6 \ - --hash=sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2 \ - --hash=sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a \ - --hash=sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30 \ - --hash=sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218 \ - --hash=sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5 \ - --hash=sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07 \ - --hash=sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2 \ - --hash=sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4 \ - --hash=sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764 \ - --hash=sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef \ - --hash=sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3 \ - --hash=sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f - # via - # -r software/thunderscope/requirements.in - # pyqtgraph -packaging==24.2 \ - --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ - --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f - # via qtpy -protobuf==6.31.1 \ - --hash=sha256:0414e3aa5a5f3ff423828e1e6a6e907d6c65c1d5b7e6e975793d5590bdeecc16 \ - --hash=sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447 \ - --hash=sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6 \ - --hash=sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402 \ - --hash=sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e \ - --hash=sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9 \ - --hash=sha256:8764cf4587791e7564051b35524b72844f845ad0bb011704c3736cce762d8fe9 \ - --hash=sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39 \ - --hash=sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a - # via -r software/thunderscope/requirements.in -pyqt-toast-notification==1.3.2 \ - --hash=sha256:135736ec0f16bff41104dee3c60ac318e5d55ae3378bf26892c6d08c36088ae6 \ - --hash=sha256:82688101202737736d51ab6c74a573b32266ecb7c8b0002f913407bd369737d9 - # via -r software/thunderscope/requirements.in -pyqt6-qt6==6.8.1 \ - --hash=sha256:006d786693d0511fbcf184a862edbd339c6ed1bb3bd9de363d73a19ed4b23dff \ - --hash=sha256:08065d595f1e6fc2dde9f4450eeff89082f4bad26f600a8e9b9cc5966716bfcf \ - --hash=sha256:1eb8460a1fdb38d0b2458c2974c01d471c1e59e4eb19ea63fc447aaba3ad530e \ - --hash=sha256:20843cb86bd94942d1cd99e39bf1aeabb875b241a35a8ab273e4bbbfa63776db \ - --hash=sha256:9f3790c4ce4dc576e48b8718d55fb8743057e6cbd53a6ca1dd253ffbac9b7287 \ - --hash=sha256:a8bc2ed4ee5e7c6ff4dd1c7db0b27705d151fee5dc232bbd1bf17618f937f515 \ - --hash=sha256:d6ca5d2b9d2ec0ee4a814b2175f641a5c4299cb80b45e0f5f8356632663f89b3 - # via -r software/thunderscope/requirements.in -pyqtdarktheme-fork==2.3.2 \ - --hash=sha256:3ea94fed5df262d960378409357c63032639f749794d766f41a45ad8558b2523 \ - --hash=sha256:d96ee64f0884678fad9b6bc352d5e37d84ca786fa60ed32ffaa7e6c6bc67e964 - # via -r software/thunderscope/requirements.in -pyqtgraph==0.13.7 \ - --hash=sha256:64f84f1935c6996d0e09b1ee66fe478a7771e3ca6f3aaa05f00f6e068321d9e3 \ - --hash=sha256:7754edbefb6c367fa0dfb176e2d0610da3ada20aa7a5318516c74af5fb72bf7a - # via -r software/thunderscope/requirements.in -qtawesome==1.4.0 \ - --hash=sha256:783e414d1317f3e978bf67ea8e8a1b1498bad9dbd305dec814027e3b50521be6 \ - --hash=sha256:a4d689fa071c595aa6184171ce1f0f847677cb8d2db45382c43129f1d72a3d93 - # via -r software/thunderscope/requirements.in -qtpy==2.4.2 \ - --hash=sha256:5a696b1dd7a354cb330657da1d17c20c2190c72d4888ba923f8461da67aa1a1c \ - --hash=sha256:9d6ec91a587cc1495eaebd23130f7619afa5cdd34a277acb87735b4ad7c65156 - # via - # pyqt-toast-notification - # qtawesome From 9022e22c6aadbd74fdffa6c994b20542f928d746 Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Sat, 7 Feb 2026 12:17:59 -0800 Subject: [PATCH 23/34] test --- .github/actions/environment-setup/action.yml | 17 +- .github/release-drafter.yml | 11 -- .github/workflows/main.yml | 10 +- .github/workflows/release.yml | 148 ------------------ .gitignore | 4 - docs/robot-software-architecture.md | 4 +- docs/setup-pi.md | 121 -------------- docs/useful-robot-commands.md | 6 +- environment_setup/macos_requirements.txt | 13 -- environment_setup/setup_software.sh | 5 - environment_setup/setup_software_mac.sh | 90 ----------- environment_setup/util.sh | 84 +++------- src/.bazelrc | 2 +- src/MODULE.bazel | 3 +- src/MODULE.bazel.lock | 8 +- .../src/amun/simulator/simulator.cpp | 5 +- src/software/BUILD | 9 -- src/software/ai/hl/stp/play/play.cpp | 3 +- .../stp/tactic/goalie/goalie_tactic_test.py | 8 +- .../pass_defender/pass_defender_fsm.cpp | 7 - src/software/embedded/BUILD | 5 - src/software/embedded/ansible/BUILD | 1 - .../embedded/ansible/tasks/setup_systemd.yml | 11 -- .../embedded/hash_thunderloop_binary.sh | 2 +- .../field_tests/field_test_fixture.py | 1 - src/software/logger/BUILD | 14 +- src/software/logger/compat_flags.h | 17 -- src/software/logger/csv_sink.cpp | 6 +- src/software/logger/log_merger.cpp | 6 +- src/software/logger/log_merger.h | 16 +- src/software/logger/logger.h | 7 +- src/software/logger/proto_logger.cpp | 4 +- .../sensor_fusion/filter/ball_filter.cpp | 15 +- .../sensor_fusion/filter/ball_filter.h | 10 +- .../simulated_tests/simulated_test_fixture.py | 2 - src/software/thunderscope/BUILD | 2 - .../binary_context_managers/BUILD | 10 -- .../binary_context_managers/full_system.py | 29 ++-- .../game_controller.py | 9 +- .../runtime_installer.py | 67 -------- .../binary_context_managers/runtime_loader.py | 139 ---------------- .../runtime_manager.py | 49 ------ src/software/thunderscope/constants.py | 14 -- src/software/thunderscope/gl/widgets/BUILD | 20 --- .../gl/widgets/gl_gamecontroller_toolbar.py | 35 ----- .../gl/widgets/gl_runtime_installer.py | 56 ------- .../gl/widgets/gl_runtime_selector.py | 100 ------------ src/software/thunderscope/requirements.in | 2 +- .../thunderscope/requirements_lock.darwin.txt | 126 --------------- .../thunderscope/requirements_lock.txt | 2 +- .../thunderscope/robot_diagnostics/BUILD | 8 +- .../robot_diagnostics/handheld_controller.py | 7 +- .../handheld_controller_widget.py | 8 +- .../thunderscope/thunderscope_main.py | 19 +-- src/software/thunderscope/util.py | 8 - 55 files changed, 106 insertions(+), 1279 deletions(-) delete mode 100644 .github/release-drafter.yml delete mode 100644 .github/workflows/release.yml delete mode 100644 docs/setup-pi.md delete mode 100644 environment_setup/macos_requirements.txt delete mode 100755 environment_setup/setup_software_mac.sh delete mode 100644 src/software/logger/compat_flags.h delete mode 100644 src/software/thunderscope/binary_context_managers/runtime_installer.py delete mode 100644 src/software/thunderscope/binary_context_managers/runtime_loader.py delete mode 100644 src/software/thunderscope/binary_context_managers/runtime_manager.py delete mode 100644 src/software/thunderscope/gl/widgets/gl_runtime_installer.py delete mode 100644 src/software/thunderscope/gl/widgets/gl_runtime_selector.py delete mode 100644 src/software/thunderscope/requirements_lock.darwin.txt diff --git a/.github/actions/environment-setup/action.yml b/.github/actions/environment-setup/action.yml index ff9cdc160b..4cf85911fd 100644 --- a/.github/actions/environment-setup/action.yml +++ b/.github/actions/environment-setup/action.yml @@ -1,12 +1,4 @@ name: Environment Setup - -on: - workflow_call: - inputs: - os: - required: true - type: string - runs: using: "composite" steps: @@ -18,15 +10,8 @@ runs: sudo rm -rf /usr/share/dotnet sudo rm -rf /usr/local/lib/android sudo rm -rf /opt/ghc - + - name: Run setup_software.sh shell: bash - if: runner.os == 'Linux' run: | "${GITHUB_WORKSPACE}"/environment_setup/setup_software.sh - - - name: Run setup_software_mac.sh - shell: bash - if: runner.os == 'macOS' - run: | - "${GITHUB_WORKSPACE}"/environment_setup/setup_software_mac.sh diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml deleted file mode 100644 index ff313816bd..0000000000 --- a/.github/release-drafter.yml +++ /dev/null @@ -1,11 +0,0 @@ -change-template: '- $TITLE @$AUTHOR (#$NUMBER)' -name-template: 'v$RESOLVED_VERSION' -tag-template: 'v$RESOLVED_VERSION' -change-title-escapes: '\<*_&' -exclude-labels: - - 'skip-changelog' - -template: | - ## Changes - - $CHANGES diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5b1c94d459..ebe85c735e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,8 +31,7 @@ jobs: -//software/ai/hl/... \ -//software/field_tests/... \ -//software/embedded/... \ - -//toolchains/cc/... \ - -//software:unix_full_system_tar_gen + -//toolchains/cc/... - name: Jetson Nano Build Test run: | @@ -61,8 +60,7 @@ jobs: -//software/ai/hl/... \ -//software/field_tests/... \ -//software/ai/navigator/... \ - -//toolchains/cc/... \ - -//software:unix_full_system_tar_gen + -//toolchains/cc/... robot-tests: name: Robot Software Tests @@ -110,8 +108,8 @@ jobs: //software:unix_full_system \ //software/simulated_tests/... \ //software/ai/hl/... \ - //software/ai/navigator/... - + //software/ai/navigator/... + - name: Upload simulated test proto logs # Ensure that simulated test logs get uploaded if: always() diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 64b7dd6151..0000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,148 +0,0 @@ -name: Release binary - -on: - schedule: - - cron: '0 0 * * 0' # Weekly Patch (Sundays) - - cron: '0 0 1 * *' # Monthly Minor (1st of the month) - workflow_dispatch: - inputs: - version_type: - description: 'Manual Release Level' - required: true - default: 'major' - type: choice - options: - - major - - minor - - patch - release_tag: - description: 'Specify the new release tag name (e.g., v1.2.3)' - required: false - type: string - - target_commit: - description: 'Optional commit SHA/branch to release (leave empty for current HEAD)' - required: false - type: string - default: '' - -jobs: - prepare_release: - runs-on: ubuntu-latest - outputs: - tag: ${{ steps.bump_logic.outputs.tag }} - should_release: ${{ steps.check.outputs.count > 0 }} - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.event.inputs.target_commit || github.sha }} - - - name: Check for recent commits - id: check - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "count=1" >> $GITHUB_OUTPUT - else - COUNT=$(git log --since="1 week ago" --oneline | wc -l) - echo "count=$COUNT" >> $GITHUB_OUTPUT - fi - - - name: Determine Version Bump - if: steps.check.outputs.count > 0 - id: bump_logic - run: | - if [ "${{ github.event.inputs.release_tag }}" = "" ]; then - BUMP="patch" - if [ "$(date +%d)" = "01" ]; then BUMP="minor"; fi - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - BUMP="${{ github.event.inputs.version_type }}" - fi - - LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v1.0.0") - # Strip the 'v' prefix - BASE_VERSION=${LATEST_TAG#v} - - IFS='.' read -r major minor patch <<< "$BASE_VERSION" - - if [ "$BUMP" = "major" ]; then - major=$((major + 1)); minor=0; patch=0 - elif [ "$BUMP" = "minor" ]; then - minor=$((minor + 1)); patch=0 - else - patch=$((patch + 1)) - fi - - NEW_TAG="v$major.$minor.$patch" - else - NEW_TAG="${{ github.event.inputs.release_tag }}" - fi - echo "tag=$NEW_TAG" >> $GITHUB_OUTPUT - echo "Using version: $NEW_TAG" - - - name: Draft Release Notes - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: release-drafter/release-drafter@v6 - with: - version: ${{ steps.bump_logic.outputs.tag }} - tag: ${{ steps.bump_logic.outputs.tag }} - - - name: Create GitHub Release - if: steps.check.outputs.count > 0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh release create "${{ steps.bump_logic.outputs.tag }}" \ - --title "${{ steps.bump_logic.outputs.tag }}" \ - --generate-notes \ - --draft - - upload_assets: - needs: prepare_release - if: needs.prepare_release.outputs.should_release == 'true' - strategy: - matrix: - platform: [ubuntu-x86, mac-arm64] - include: - - platform: ubuntu-x86 - runner: ubuntu-24.04 - - platform: mac-arm64 - runner: macos-latest - runs-on: ${{ matrix.runner }} - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.event.inputs.target_commit || github.ref }} - - - uses: ./.github/actions/environment-setup - - - name: Build Binaries - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd src - TAG="${{ needs.prepare_release.outputs.tag }}" - bazel build --show_timestamps --copt=-O3 --verbose_failures \ - -- //software:unix_full_system_tar_gen - mv bazel-bin/software/unix_full_system_tar_gen.tar.gz "${{ runner.temp }}/unix_full_system_${{ needs.prepare_release.outputs.tag }}_${{ matrix.platform }}.tar.gz" - gh release upload "$TAG" "${{ runner.temp }}/unix_full_system_${{ needs.prepare_release.outputs.tag }}_${{ matrix.platform }}.tar.gz" - - publish_release: - needs: [prepare_release, upload_assets] - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.inputs.target_commit || github.sha }} - - name: Undraft Release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: gh release edit "${{ needs.prepare_release.outputs.tag }}" --draft=false diff --git a/.gitignore b/.gitignore index 076a0b3477..989fc31f59 100644 --- a/.gitignore +++ b/.gitignore @@ -158,7 +158,3 @@ src/.clangd # external is a special directory for bazel so it should be ignored src/external - -# macOS folder info -.DS_Store - diff --git a/docs/robot-software-architecture.md b/docs/robot-software-architecture.md index bacc1d1825..08f9ecd6bd 100644 --- a/docs/robot-software-architecture.md +++ b/docs/robot-software-architecture.md @@ -35,7 +35,9 @@ More commands available [here](useful-robot-commands.md#off-robot-commands) ## Systemd -[Systemd](https://www.freedesktop.org/wiki/Software/systemd/) allows us to have services which start as soon as we boot the robot, will automatically restart and are individually controllable. All services have the file {service}.service, which controls the configuration of that service. Our core service brought up by systemd is thunderloop. The thunderloop.service file can be seen [here](https://github.com/UBC-Thunderbots/Software/blob/master/src/software/embedded/linux_configs/systemd/thunderloop.service). +[Systemd](https://www.freedesktop.org/wiki/Software/systemd/) allows us to have services which start as soon as we boot the robot, will automatically restart and are individually controllable. All services have the file {service}.service, which controls the configuration of that service. Currently we have a service for thunderloop, announcements and display + +To learn more about how it works, [see the RFC](https://docs.google.com/document/d/1hN3Us2Vjr8z6ihqUVp_3L7rrjKc-EZ-l2hZJc31gNOc/edit) ## Redis diff --git a/docs/setup-pi.md b/docs/setup-pi.md deleted file mode 100644 index d4549e41fc..0000000000 --- a/docs/setup-pi.md +++ /dev/null @@ -1,121 +0,0 @@ -# Raspberry Pi Setup Guide - -## Install Raspberry Pi OS onto MicroSD card - -### Introduction - -The microSD card in a Raspberry Pi holds its Operating System and acts as its primary storage area. The Pi will not work without it. - -The Raspberry Pi 5 is compatible with many different (primarily Linux-based) operating systems. The one we use is the standard Raspberry Pi OS (also Linux-based). - -### Install OS Using Raspberry Pi Imager - -To install the Raspberry Pi OS onto a microSD card, we use the Raspberry Pi Imager program. Install it [here](https://www.raspberrypi.com/software/). - -*Note: if using the "Download for Linux" option, you will have to right click on the installed `.AppImage` file and select `Properties` → `Permissions` → `Allow Executing File as Program` (this may appear as a checkbox) - -When you open the imager, you should see something like this: - -* image - -Once installed, follow these steps to set up the Raspberry Pi: -1. Plug the microSD card into your device. - * If your device has an SD card slot, use a microSD to SD card adapter. This adapter is available in the tbots EDC space as of Jan 2026. - * If your device only has a USB slot, use a combination of a microSD to SD card adapter and USB SD card reader. Again, both of these adapters are available in the tbots EDC space. -2. Open and configure the Raspberry Pi Imager program as follows: - * Raspberry Pi Device: Raspberry Pi 5 - * Operating System: Raspberry Pi OS (64-bit) - * Storage: `` - * Customization: - * Hostname: ``.local (Ex: `aimbot`.local). The `local` part is autofilled by the Raspberry Pi Imager. - * Localization → Timezone: America/Vancouver - * User → Username: robot - * User → Password: `` (ask someone if you don't know what the correct password is) - * WiFi → Secure Network → SSID: `` (`tbots` as of Jan 2026) - * WiFi → Password: `` (ask someone if you don't know what the correct password is) - * Remote Access: Enable SSH and select "Use Password Authentication" - * Click the "Write" button and wait until complete - - Once the write process is complete, the Raspberry Pi OS is set up! You can verify the write was successful using several methods; the following is only one of these methods: - * Connect the Raspberry Pi to an HDMI output screen using a microHDMI to HDMI cable. - * You can also use an HDMI to HDMI cable with a microHDMI to HDMI adapter. We have one of these adapters in the tbots EDC space as of Jan 2026. - * The output screen on the HDMI should appear similar to a default laptop/PC screen. If this is the case, the write was successful. - * If you do not see the above, and instead see text on a black screen with messages akin to "Unable to read partition as FAT" - the write was not successful. - -## Configure Raspberry Pi with Thunderbots Service - -### Introduction - -We use the Raspberry Pi OS, which is based on the Linux kernel. The Linux kernel uses systemd as its service manager. Our core service is [thunderloop.service](https://github.com/UBC-Thunderbots/Software/blob/master/src/software/embedded/linux_configs/systemd/thunderloop.service). - -To run and manage the thunderloop service on the Pi, however, there are many dependencies (drivers, admin control, internet, etc.) that must be set up first. Luckily, all of this setup can be done using one command with an Ansible playbook. - -Read more about Ansible in our robot software architecture documentation [here](https://github.com/UBC-Thunderbots/Software/blob/master/docs/robot-software-architecture.md#ansible). - -### Configuration - -#### Internet Configuration - -Before running the Ansible command, we must do the following to configure internet access to the Pi: -1. Ensure your Raspberry Pi is connected to internet through an ethernet cable. Do this by connecting one end to the Rapsberry Pi and the other to your device (PC/laptop). Ensure your ethernet cable is not broken, as there are many non-functioning ones in the Thunderbots EDC space. -2. On your device (PC/laptop), configure network settings through the following steps: - 1. Go to network settings. You should see something like this: - - * image - - 2. Click the settings icon on the right (seen in the image above). - 3. Under the IPv4 tab, select "Shared to other computers" - - * image - - 4. Under the IPv6 tab, select "Disable" - * image - - 5. Apply changes -3. Enable IP forwarding from your device to the Pi with the following command (this has to be reconfigured every time you boot your device): -```bash -sudo sysctl -w net.ipv4.ip_forward=1 -``` -4. Enable Network Address Translation (NAT) between your Pi and device with the following commands (this has to be reconfigured every time you boot your device): -```bash -sudo iptables -t nat -A POSTROUTING -o wlo1 -j MASQUERADE -sudo iptables -A FORWARD -i eno2 -o wlo1 -j ACCEPT -sudo iptables -A FORWARD -i wlo1 -o eno2 -m state --state RELATED,ESTABLISHED -j ACCEPT -``` - -#### Thunderloop Service Configuration Through Ansible -1. SSH into the Raspberry Pi from your device by connecting to the `tbots` WiFi and typing the following in the command terminal: - * ssh `robot@` (Ex: `ssh robot@aimbot.local`) - * enter the password when prompted -2. Verify that the Pi has internet connection by pinging Google's Public DNS Service with the following command: -```bash -ping -c 3 8.8.8.8 -``` - * If successful, you should see all packets transmitted and received at some point in the return message. Ex: ```3 packets transmitted, 3 received, 0% packet loss, time 2004ms```. - * If not successful, you will see packets not received. Ex: ```3 packets transmitted, 0 received, 100% packet loss, time 2052ms```. -3. Exit the SSH connection to the Raspberry Pi with the `exit` command. -4. Change directory to `Software/src` and run the bazel ansible command: -```bash -bazel run //software/embedded/ansible:run_ansible --platforms=//toolchains/cc:robot --//software/embedded:host_platform=PI -- --playbook setup_pi.yml --hosts .local --ssh_pass -``` - * Ensure you configure `` and `` in the command above. Copy/pasting the above won't work. - * This may take a while. -5. Done! - -### Common Errors and Debugging - -**Cannot SSH into Pi** -A: Confirm that your device is connected to the `tbots` WiFi - -**Raspberry Pi shuts off (light turns red, HDMI output disconnects if connected) while the Bazel Ansible command is running** -A: This is a power brownout (voltage drops below required threshold). There are too many peripherals connected. Disconnect some and try again. - -**Raspberry Pi unresponsive and LED always solid green** -A: This is usually an indicator that the Pi's SD Card is corrupted or empty. Operational Raspberry Pi's usually have a flickering LED. Fix by reprovisioning the SD Card with the Raspberry Pi Imager (directions above). - - - - - - - diff --git a/docs/useful-robot-commands.md b/docs/useful-robot-commands.md index 1ede1bcd24..cf9415ab38 100644 --- a/docs/useful-robot-commands.md +++ b/docs/useful-robot-commands.md @@ -156,7 +156,7 @@ bazel run //software/embedded/ansible:run_ansible --platforms=//toolchains/cc:ro ### Raspberry Pi ```bash -bazel run //software/embedded/ansible:run_ansible --platforms=//toolchains/cc:robot --//software/embedded:host_platform=PI -- --playbook setup_pi.yml --hosts --ssh_pass +bazel run //software/embedded/ansible:run_ansible --platforms=//toolchains/cc:robot --//software/embedded:host_platform=PI -- --playbook setup_raspberry_pi.yml --hosts --ssh_pass ``` ## Robot Diagnostics @@ -189,7 +189,7 @@ Runs the robot auto test fixture on a robot through Ansible, which tests the mot From Software/src: ```bash -bazel run //software/embedded/ansible:run_ansible --//software/embedded:host_platform= --platforms=//toolchains/cc:robot -- --playbook robot_auto_test_playbook.yml --hosts --ssh_pass +bazel run //software/embedded/ansible:run_ansible --//software/embedded:host_platform= --platforms=//toolchains/cc:robot -- --playbook robot_auto_test_playbook.yml --hosts --ssh_pass ``` * replace the \ with the target platform for the robot (either `PI` or `NANO`) @@ -202,7 +202,7 @@ bazel run //software/embedded/ansible:run_ansible --//software/embedded:host_pla ## Systemd Services -Status shows whether the service is running and some recent logs. More logs can be found using `journalctl` shown below. More control can be achieved with `systemctl`. Currently, the only valid `` is `thunderloop`. +Status shows whether the service is running and some recent logs. More logs can be found using `journalctl` shown below. More control can be achieved with `systemctl`. Valid `` are `thunderloop`, `display`, and `wifi_announcements` ```bash service status diff --git a/environment_setup/macos_requirements.txt b/environment_setup/macos_requirements.txt deleted file mode 100644 index 8e4cf0bb42..0000000000 --- a/environment_setup/macos_requirements.txt +++ /dev/null @@ -1,13 +0,0 @@ -ansible-lint==24.12.2 -pyqtgraph==0.13.7 -thefuzz==0.19.0 -iterfzf==0.5.0.20.0 -python-Levenshtein==0.25.1 -psutil==5.9.0 -PyOpenGL==3.1.6 -ruff==0.5.5 -pyqt-toast-notification==1.3.2 -grpcio-tools==1.71.0 -platformio==6.1.18 -pyqt6==6.9.1 - diff --git a/environment_setup/setup_software.sh b/environment_setup/setup_software.sh index 2a8c9a5660..f3701b7087 100755 --- a/environment_setup/setup_software.sh +++ b/environment_setup/setup_software.sh @@ -143,10 +143,6 @@ sudo cp "$CURR_DIR/../src/software/autoref/DIV_B.txt" "/opt/tbotspython/autoRefe print_status_msg "Finished setting up AutoRef" -# setup external_runtimes -sudo mkdir /opt/tbotspython/external_runtimes -sudo chown -R $USER:$USER /opt/tbotspython/external_runtimes/ - # Install Bazel print_status_msg "Installing Bazel" @@ -164,7 +160,6 @@ print_status_msg "Done setting up cross compiler for robot software" print_status_msg "Setting Up Python Development Headers" install_python_dev_cross_compile_headers $g_arch -install_python_toolchain_headers print_status_msg "Done Setting Up Python Development Headers" print_status_msg "Setting Up PlatformIO" diff --git a/environment_setup/setup_software_mac.sh b/environment_setup/setup_software_mac.sh deleted file mode 100755 index b714235023..0000000000 --- a/environment_setup/setup_software_mac.sh +++ /dev/null @@ -1,90 +0,0 @@ -#!/bin/bash -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~# -# UBC Thunderbots macOS Software Setup -# -# This script will install all required libraries and dependencies to build -# and run the Thunderbots codebase on macOS, including the AI and unit tests. -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~# - -# Save the parent dir of this script -CURR_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) -cd "$CURR_DIR" || exit - -source util.sh - -# Since we only support MacOS Arm chips (M series), we can use "Darwin" as the identifier -# for mac setup procedures and ignore the architecture for now. -sys=$(uname -s) - -print_status_msg "Installing Utilities and Dependencies" - -# Update Homebrew -brew update - -# Install required packages -host_software_packages=( - cmake@4 - python@3.12 - bazelisk - openjdk@21 - pyqt@6 - qt@6 - node@20 - go@1.24 - clang-format@20 -) - -for pkg in "${host_software_packages[@]}"; do - if ! brew list "$pkg" &>/dev/null; then - print_status_msg "Installing $pkg..." - brew install "$pkg" - else - print_status_msg "$pkg already installed, skipping..." - fi -done - -# Set up cache -mkdir /tmp/tbots_download_cache - -# Set up Python -print_status_msg "Setting Up Python Environment" - -# Create virtual environment -sudo python3.12 -m venv /opt/tbotspython -chmod -source /opt/tbotspython/bin/activate - -# Install Python dependencies -sudo pip install --upgrade pip -sudo pip install -r macos_requirements.txt - -print_status_msg "Done Setting Up Python Environment" - -print_status_msg "Fetching game controller" -install_gamecontroller $sys - -print_status_msg "Setting up TIGERS AutoRef" -install_autoref $sys -sudo chmod +x "$CURR_DIR/../src/software/autoref/run_autoref.sh" -sudo cp "$CURR_DIR/../src/software/autoref/DIV_B.txt" "/opt/tbotspython/autoReferee/config/geometry/DIV_B.txt" -print_status_msg "Finished setting up AutoRef" - -print_status_msg "Setting up cross compiler for robot software" -install_cross_compiler $sys -print_status_msg "Done setting up cross compiler for robot software" - -print_status_msg "Setting Up Python Development Headers" -install_python_toolchain_headers -print_status_msg "Done Setting Up Python Development Headers" - -print_status_msg "Granting Permissions to /opt/tbotspython" -sudo chown -R $(id -u):$(id -g) /opt/tbotspython -print_status_msg "Done Granting Permissions to /opt/tbotspython" - -print_status_msg "Set up ansible-lint" -/opt/tbotspython/bin/ansible-galaxy collection install ansible.posix -print_status_msg "Finished setting up ansible-lint" - -print_status_msg "Software Setup Complete" -print_status_msg "Note: Some changes require a new terminal session to take effect" - diff --git a/environment_setup/util.sh b/environment_setup/util.sh index fb85ba8a2b..e3603b5510 100755 --- a/environment_setup/util.sh +++ b/environment_setup/util.sh @@ -1,20 +1,11 @@ install_autoref() { - if is_darwin $1; then - autoref_version=1.5.5 - curl -L https://github.com/TIGERs-Mannheim/AutoReferee/releases/download/${autoref_version}/autoReferee.zip -o /tmp/tbots_download_cache/autoReferee.zip - unzip -q -o -d /tmp/tbots_download_cache/ /tmp/tbots_download_cache/autoReferee.zip + autoref_commit=b30660b78728c3ce159de8ae096181a1ec52e9ba + wget -N https://github.com/TIGERs-Mannheim/AutoReferee/archive/${autoref_commit}.zip -O /tmp/tbots_download_cache/autoReferee.zip + unzip -q -o -d /tmp/tbots_download_cache/ /tmp/tbots_download_cache/autoReferee.zip - sudo mv /tmp/tbots_download_cache/autoReferee /opt/tbotspython/ - rm -rf /tmp/tbots_download_cache/autoReferee.zip - else - autoref_commit=b30660b78728c3ce159de8ae096181a1ec52e9ba - wget -N https://github.com/TIGERs-Mannheim/AutoReferee/archive/${autoref_commit}.zip -O /tmp/tbots_download_cache/autoReferee.zip - unzip -q -o -d /tmp/tbots_download_cache/ /tmp/tbots_download_cache/autoReferee.zip - - /tmp/tbots_download_cache/AutoReferee-${autoref_commit}/./gradlew installDist -p /tmp/tbots_download_cache/AutoReferee-${autoref_commit} -Dorg.gradle.java.home=/opt/tbotspython/bin/jdk - mv /tmp/tbots_download_cache/AutoReferee-${autoref_commit}/build/install/autoReferee /opt/tbotspython/ - rm -rf /tmp/tbots_download_cache/autoReferee.zip /tmp/tbots_download_cache/AutoReferee-${autoref_commit} - fi + /tmp/tbots_download_cache/AutoReferee-${autoref_commit}/./gradlew installDist -p /tmp/tbots_download_cache/AutoReferee-${autoref_commit} -Dorg.gradle.java.home=/opt/tbotspython/bin/jdk + mv /tmp/tbots_download_cache/AutoReferee-${autoref_commit}/build/install/autoReferee /opt/tbotspython/ + rm -rf /tmp/tbots_download_cache/autoReferee.zip /tmp/tbots_download_cache/AutoReferee-${autoref_commit} } install_bazel() { @@ -34,47 +25,25 @@ install_clang_format() { install_cross_compiler() { file_name=aarch64-tbots-linux-gnu-for-aarch64 - if is_darwin $1; then - full_file_name=$file_name.tar.xz - curl -L "https://raw.githubusercontent.com/UBC-Thunderbots/Software-External-Dependencies/refs/heads/main/toolchain/$full_file_name" \ - -o /tmp/tbots_download_cache/$full_file_name - tar -xf /tmp/tbots_download_cache/$full_file_name -C /tmp/tbots_download_cache/ - sudo mv /tmp/tbots_download_cache/aarch64-tbots-linux-gnu /opt/tbotspython - rm /tmp/tbots_download_cache/$full_file_name - else - if is_x86 $1; then - file_name=aarch64-tbots-linux-gnu-for-x86 - fi - full_file_name=$file_name.tar.xz - wget https://raw.githubusercontent.com/UBC-Thunderbots/Software-External-Dependencies/refs/heads/main/toolchain/$full_file_name -O /tmp/tbots_download_cache/$full_file_name - tar -xf /tmp/tbots_download_cache/$full_file_name -C /tmp/tbots_download_cache/ - sudo mv /tmp/tbots_download_cache/aarch64-tbots-linux-gnu /opt/tbotspython - rm /tmp/tbots_download_cache/$full_file_name + if is_x86 $1; then + file_name=aarch64-tbots-linux-gnu-for-x86 fi + full_file_name=$file_name.tar.xz + wget https://raw.githubusercontent.com/UBC-Thunderbots/Software-External-Dependencies/refs/heads/main/toolchain/$full_file_name -O /tmp/tbots_download_cache/$full_file_name + tar -xf /tmp/tbots_download_cache/$full_file_name -C /tmp/tbots_download_cache/ + sudo mv /tmp/tbots_download_cache/aarch64-tbots-linux-gnu /opt/tbotspython + rm /tmp/tbots_download_cache/$full_file_name } install_gamecontroller () { - if is_darwin $1; then - curl -L https://github.com/RoboCup-SSL/ssl-game-controller/archive/refs/tags/v3.17.0.zip -o /tmp/tbots_download_cache/ssl-game-controller.zip - unzip -q -o -d /tmp/tbots_download_cache/ /tmp/tbots_download_cache/ssl-game-controller.zip - cd /tmp/tbots_download_cache/ssl-game-controller-3.17.0 - make install - go build cmd/ssl-game-controller/main.go - sudo mv main /opt/tbotspython/gamecontroller - sudo chmod +x /opt/tbotspython/gamecontroller - - cd - - sudo rm -rf /tmp/tbots_download_cache/ssl-game-controller-3.17.0 /tmp/tbots_download_cache/go /tmp/tbots_download_cache/go.tar.gz /tmp/tbots_download_cache/ssl-game-controller.zip - else - arch=arm64 - if is_x86 $1; then - arch=amd64 - fi - - wget https://github.com/RoboCup-SSL/ssl-game-controller/releases/download/v3.16.1/ssl-game-controller_v3.16.1_linux_${arch} -O /tmp/tbots_download_cache/gamecontroller - sudo mv /tmp/tbots_download_cache/gamecontroller /opt/tbotspython/gamecontroller - sudo chmod +x /opt/tbotspython/gamecontroller + arch=arm64 + if is_x86 $1; then + arch=amd64 fi + + wget https://github.com/RoboCup-SSL/ssl-game-controller/releases/download/v3.16.1/ssl-game-controller_v3.16.1_linux_${arch} -O /tmp/tbots_download_cache/gamecontroller + sudo mv /tmp/tbots_download_cache/gamecontroller /opt/tbotspython/gamecontroller + sudo chmod +x /opt/tbotspython/gamecontroller } install_java () { @@ -121,11 +90,6 @@ install_python_dev_cross_compile_headers() { rm -rf /tmp/tbots_download_cache/python-3.12.0.tar.xz } -install_python_toolchain_headers() { - sudo mkdir -p /opt/tbotspython/py_headers/include/ - sudo ln -sfn "$(python3.12-config --includes | awk '{for(i=1;i<=NF;++i) if ($i ~ /^-I/) print substr($i, 3)}' | head -n1)" /opt/tbotspython/py_headers/include/ -} - is_x86() { if [[ $1 == "x86_64" ]]; then return 0 @@ -134,14 +98,6 @@ is_x86() { fi } -is_darwin() { - if [[ $1 == "Darwin" ]]; then - return 0 - else - return 1 - fi -} - print_status_msg () { echo "================================================================" echo $1 diff --git a/src/.bazelrc b/src/.bazelrc index 28695e559f..03d0b13b5b 100644 --- a/src/.bazelrc +++ b/src/.bazelrc @@ -66,7 +66,7 @@ build --incompatible_remove_legacy_whole_archive=False # Escalate Warnings to fail Compile for Thunderbots code build --features=external_include_paths -build:linux --per_file_copt=proto/.*,proto/message_translation/.*,proto/primitive/.*,software/.*,shared/.*,-external/.*@-Wall,-Wextra,-Wno-unused-parameter,-Wno-deprecated,-Werror,-Wno-deprecated-declarations +build --per_file_copt=proto/.*,proto/message_translation/.*,proto/primitive/.*,software/.*,shared/.*,-external/.*@-Wall,-Wextra,-Wno-unused-parameter,-Wno-deprecated,-Werror,-Wno-deprecated-declarations # TODO: #3492 # build --per_file_copt=software/.*,shared/.*,-external/.*@-Wconversion diff --git a/src/MODULE.bazel b/src/MODULE.bazel index c7c7167ef2..7c1597ca68 100644 --- a/src/MODULE.bazel +++ b/src/MODULE.bazel @@ -22,7 +22,6 @@ bazel_dep(name = "rules_proto", version = "7.1.0") bazel_dep(name = "yaml-cpp", version = "0.8.0") bazel_dep(name = "buildifier_prebuilt", version = "8.0.3") bazel_dep(name = "pybind11_protobuf", version = "0.0.0-20250210-f02a2b7") -bazel_dep(name = "bazel_lib", version = "3.1.0") ############################################## # Load PIP packages and our Requirements @@ -269,7 +268,7 @@ new_local_repository( new_local_repository( name = "py_cc_toolchain_host", build_file = "@//extlibs:py_cc_toolchain.BUILD", - path = "/opt/tbotspython/py_headers/include/python3.12/", + path = "/usr/include/python3.12/", ) new_local_repository( diff --git a/src/MODULE.bazel.lock b/src/MODULE.bazel.lock index 180f9e27bf..ce4bc12db9 100644 --- a/src/MODULE.bazel.lock +++ b/src/MODULE.bazel.lock @@ -41,8 +41,6 @@ "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", "https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", - "https://bcr.bazel.build/modules/bazel_lib/3.1.0/MODULE.bazel": "6809765c14e3c766a9b9286c7b0ec56ed87a73326e48fe01749f0c0fdcfe3287", - "https://bcr.bazel.build/modules/bazel_lib/3.1.0/source.json": "aaf7c2dc816219f4cb356c9d65f2555fb7f9543e537199f74a921f7877d23dfb", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", @@ -54,8 +52,7 @@ "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", - "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", - "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/source.json": "7ebaefba0b03efe59cac88ed5bbc67bcf59a3eff33af937345ede2a38b2d368a", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953", "https://bcr.bazel.build/modules/boringssl/0.0.0-20211025-d4f1ab9/MODULE.bazel": "6ee6353f8b1a701fe2178e1d925034294971350b6d3ac37e67e5a7d463267834", "https://bcr.bazel.build/modules/boringssl/0.0.0-20230215-5c22014/MODULE.bazel": "4b03dc0d04375fa0271174badcd202ed249870c8e895b26664fd7298abea7282", "https://bcr.bazel.build/modules/boringssl/0.0.0-20240530-2db0eb3/MODULE.bazel": "d0405b762c5e87cd445b7015f2b8da5400ef9a8dbca0bfefa6c1cea79d528a97", @@ -223,8 +220,7 @@ "https://bcr.bazel.build/modules/rules_rust/0.45.1/MODULE.bazel": "a69d0db3a958fab2c6520961e1b2287afcc8b36690fd31bbc4f6f7391397150d", "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", - "https://bcr.bazel.build/modules/rules_shell/0.4.1/MODULE.bazel": "00e501db01bbf4e3e1dd1595959092c2fadf2087b2852d3f553b5370f5633592", - "https://bcr.bazel.build/modules/rules_shell/0.4.1/source.json": "4757bd277fe1567763991c4425b483477bb82e35e777a56fd846eb5cceda324a", + "https://bcr.bazel.build/modules/rules_shell/0.3.0/source.json": "c55ed591aa5009401ddf80ded9762ac32c358d2517ee7820be981e2de9756cf3", "https://bcr.bazel.build/modules/rules_swift/1.16.0/MODULE.bazel": "4a09f199545a60d09895e8281362b1ff3bb08bbde69c6fc87aff5b92fcc916ca", "https://bcr.bazel.build/modules/rules_swift/2.1.1/MODULE.bazel": "494900a80f944fc7aa61500c2073d9729dff0b764f0e89b824eb746959bc1046", "https://bcr.bazel.build/modules/rules_swift/2.1.1/source.json": "40fc69dfaac64deddbb75bd99cdac55f4427d9ca0afbe408576a65428427a186", diff --git a/src/extlibs/er_force_sim/src/amun/simulator/simulator.cpp b/src/extlibs/er_force_sim/src/amun/simulator/simulator.cpp index 1f64796560..2c00c5a798 100644 --- a/src/extlibs/er_force_sim/src/amun/simulator/simulator.cpp +++ b/src/extlibs/er_force_sim/src/amun/simulator/simulator.cpp @@ -677,13 +677,12 @@ void Simulator::handleSimulatorSetupCommand(const std::unique_ptr if (realism.has_vision_delay()) { - m_visionDelay = std::max(0l, realism.vision_delay()); + m_visionDelay = std::max(0l, realism.vision_delay()); } if (realism.has_vision_processing_time()) { - m_visionProcessingTime = - std::max(0l, realism.vision_processing_time()); + m_visionProcessingTime = std::max(0l, realism.vision_processing_time()); } if (realism.has_simulate_dribbling()) diff --git a/src/software/BUILD b/src/software/BUILD index dbafc6f2e8..dbb2297674 100644 --- a/src/software/BUILD +++ b/src/software/BUILD @@ -1,6 +1,4 @@ -load("@bazel_lib//lib:write_source_files.bzl", "write_source_files") load("@pybind11_bazel//:build_defs.bzl", "pybind_extension", "pybind_library") -load("@rules_pkg//pkg:tar.bzl", "pkg_tar") load("@thunderscope_deps//:requirements.bzl", "requirement") package(default_visibility = ["//visibility:public"]) @@ -29,13 +27,6 @@ cc_binary( ], ) -pkg_tar( - name = "unix_full_system_tar_gen", - srcs = [":unix_full_system"], - extension = "tar.gz", - mode = "0755", -) - cc_binary( name = "er_force_simulator_main", srcs = ["er_force_simulator_main.cpp"], diff --git a/src/software/ai/hl/stp/play/play.cpp b/src/software/ai/hl/stp/play/play.cpp index bdeccf8b35..2d60cde13d 100644 --- a/src/software/ai/hl/stp/play/play.cpp +++ b/src/software/ai/hl/stp/play/play.cpp @@ -207,8 +207,7 @@ std::unique_ptr Play::get( new_primitives_to_assign->robot_primitives()) { primitives_to_run->mutable_robot_primitives()->insert( - google::protobuf::MapPair( - robot_id, primitive)); + google::protobuf::MapPair(robot_id, primitive)); } robots = remaining_robots; diff --git a/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py b/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py index ee0640db37..c362026220 100644 --- a/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py @@ -30,19 +30,15 @@ # test ball very fast misses net (tbots_cpp.Point(0, 0), tbots_cpp.Vector(-5, 1), tbots_cpp.Point(-4.5, 0)), # test ball very fast get saved - # TODO (#3377): This test is flaky due to inconsistent goalie reach. The linked ticket may provide a permanent fix. ( tbots_cpp.Point(-2.5, 0), - # TODO Revert velocity to (-4.8, 1.1) - tbots_cpp.Vector(-3.6, 0.825), + tbots_cpp.Vector(-4.8, 1.1), tbots_cpp.Point(-4.5, 0), ), # test ball very fast with the goalie out of position saved - # TODO (#3377): This test is flaky due to inconsistent goalie reach. The linked ticket may provide a permanent fix. ( tbots_cpp.Point(-2, 0), - # TODO Revert velocity to (-5.5,1) - tbots_cpp.Vector(-4.125, 0.75), + tbots_cpp.Vector(-5.5, 1), tbots_cpp.Point(-4.5, -0.1), ), # ball slow inside friendly defense area diff --git a/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_fsm.cpp b/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_fsm.cpp index b14bb17439..a14599c8a2 100644 --- a/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_fsm.cpp +++ b/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_fsm.cpp @@ -4,7 +4,6 @@ #include "software/ai/evaluation/intercept.h" #include "software/ai/hl/stp/tactic/move_primitive.h" #include "software/geom/algorithms/closest_point.h" -#include "software/geom/algorithms/contains.h" PassDefenderFSM::PassDefenderFSM( std::shared_ptr ai_config_ptr) @@ -19,12 +18,6 @@ bool PassDefenderFSM::passStarted(const Update& event) event.control_params.position_to_block_from.x() - ball_position.x(), event.control_params.position_to_block_from.y() - ball_position.y()); - // Make sure ball is within playing area - if (!contains(event.common.world_ptr->field().fieldLines(), ball_position)) - { - return false; - } - bool pass_started = event.common.world_ptr->ball().hasBallBeenKicked( ball_receiver_point_vector.orientation(), MIN_PASS_SPEED, MAX_PASS_ANGLE_DIFFERENCE); diff --git a/src/software/embedded/BUILD b/src/software/embedded/BUILD index f0fa408d47..f3efd2e985 100644 --- a/src/software/embedded/BUILD +++ b/src/software/embedded/BUILD @@ -110,11 +110,6 @@ sh_binary( srcs = ["setup_robot_software_deps.sh"], ) -filegroup( - name = "hash_thunderloop_binary", - srcs = ["hash_thunderloop_binary.sh"], -) - cc_test( name = "test_battery", srcs = ["battery_test.cpp"], diff --git a/src/software/embedded/ansible/BUILD b/src/software/embedded/ansible/BUILD index 31ec3924a0..f20fb92ee5 100644 --- a/src/software/embedded/ansible/BUILD +++ b/src/software/embedded/ansible/BUILD @@ -18,7 +18,6 @@ py_binary( data = [ ":playbooks", ":tasks", - "//software/embedded:hash_thunderloop_binary.sh", "//software/embedded:setup_robot_software_deps", "//software/embedded:thunderloop_main", "//software/embedded/linux_configs/jetson_nano:jetson_nano_files", diff --git a/src/software/embedded/ansible/tasks/setup_systemd.yml b/src/software/embedded/ansible/tasks/setup_systemd.yml index eeaaa1d16c..2f86fb75b5 100644 --- a/src/software/embedded/ansible/tasks/setup_systemd.yml +++ b/src/software/embedded/ansible/tasks/setup_systemd.yml @@ -15,17 +15,6 @@ dest: ~/thunderbots_binaries/ copy_links: true - - name: Sync Thunderloop MD5 Hash - ansible.posix.synchronize: - src: "{{ playbook_dir }}/../../hash_thunderloop_binary.sh" - dest: "~/hash_thunderloop_binary.sh" - copy_links: true - - - name: Make MD5 Hash Script Executable - ansible.builtin.file: - path: "~/hash_thunderloop_binary.sh" - mode: "0755" - - name: Compute Thunderloop MD5 Hash ansible.builtin.command: "/home/{{ ansible_user }}/hash_thunderloop_binary.sh" register: result diff --git a/src/software/embedded/hash_thunderloop_binary.sh b/src/software/embedded/hash_thunderloop_binary.sh index 4fbb2206d8..8100f6cf3b 100644 --- a/src/software/embedded/hash_thunderloop_binary.sh +++ b/src/software/embedded/hash_thunderloop_binary.sh @@ -14,7 +14,7 @@ a60a083f11289d0904c9c4bf8fa59a59 thunderloop using the awk command filters out only the first argument passed to it, in this case the MD5 hash ' -hash=$(md5sum ~/thunderbots_binaries/thunderloop_main | awk '{ print $1}') +hash=$(md5sum ~/thunderbots_binaries/thunderloop | awk '{ print $1}') run_date=$(date '+%x %H:%M') echo "$hash" > ~/thunderbots_hashes/thunderloop.hash diff --git a/src/software/field_tests/field_test_fixture.py b/src/software/field_tests/field_test_fixture.py index 42c0c9f6ee..829216fde2 100644 --- a/src/software/field_tests/field_test_fixture.py +++ b/src/software/field_tests/field_test_fixture.py @@ -357,7 +357,6 @@ def field_test_runner(): # Launch all binaries with FullSystem( - "software/unix_full_system", full_system_runtime_dir=runtime_dir, debug_full_system=debug_full_sys, friendly_colour_yellow=args.run_yellow, diff --git a/src/software/logger/BUILD b/src/software/logger/BUILD index 6fa65a6909..d10935a852 100644 --- a/src/software/logger/BUILD +++ b/src/software/logger/BUILD @@ -9,7 +9,6 @@ cc_library( "log_merger.h", ], deps = [ - ":compat_flags", "@g3log", ], ) @@ -20,13 +19,9 @@ cc_library( "custom_logging_levels.h", "logger.h", ], - linkopts = select({ - "@platforms//os:linux": ["-lstdc++fs"], - "//conditions:default": [], - }), + linkopts = ["-lstdc++fs"], deps = [ ":coloured_cout_sink", - ":compat_flags", ":csv_sink", ":log_merger", ":plotjuggler_sink", @@ -108,7 +103,6 @@ cc_library( "custom_logging_levels.h", ], deps = [ - ":compat_flags", "@g3log", ], ) @@ -188,7 +182,6 @@ cc_library( "proto_logger.h", ], deps = [ - ":compat_flags", "//proto:tbots_cc_proto", "//software/multithreading:thread_safe_buffer", "@base64", @@ -196,8 +189,3 @@ cc_library( "@zlib", ], ) - -cc_library( - name = "compat_flags", - srcs = ["compat_flags.h"], -) diff --git a/src/software/logger/compat_flags.h b/src/software/logger/compat_flags.h deleted file mode 100644 index c57d8854d2..0000000000 --- a/src/software/logger/compat_flags.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include - -#if defined(__APPLE__) -using Clock = std::chrono::system_clock; -#else -using Clock = std::chrono::_V2::system_clock; -#endif - -#if __cplusplus > 201703L -#include -namespace fs = std::filesystem; -#else -#include -namespace fs = std::experimental::filesystem; -#endif diff --git a/src/software/logger/csv_sink.cpp b/src/software/logger/csv_sink.cpp index 345442d681..07494a78fb 100644 --- a/src/software/logger/csv_sink.cpp +++ b/src/software/logger/csv_sink.cpp @@ -1,6 +1,10 @@ #include "software/logger/csv_sink.h" -#include "compat_flags.h" +#if __cplusplus > 201703L +#include +#else +#include +#endif CSVSink::CSVSink(const std::string& log_directory) : log_directory(log_directory) {} diff --git a/src/software/logger/log_merger.cpp b/src/software/logger/log_merger.cpp index 4139e4fea5..8f82680cfa 100644 --- a/src/software/logger/log_merger.cpp +++ b/src/software/logger/log_merger.cpp @@ -11,7 +11,8 @@ std::list LogMerger::log(g3::LogMessage &log) { std::string msg = log.message(); - Clock::time_point current_time = std::chrono::system_clock::now(); + std::chrono::_V2::system_clock::time_point current_time = + std::chrono::system_clock::now(); // add passed time from testing current_time += passed_time; std::list messages_to_log = _getOldMessages(current_time); @@ -37,7 +38,8 @@ std::list LogMerger::log(g3::LogMessage &log) } } -std::list LogMerger::_getOldMessages(Clock::time_point current_time) +std::list LogMerger::_getOldMessages( + std::chrono::_V2::system_clock::time_point current_time) { std::list result; while (message_list.size() > 0) diff --git a/src/software/logger/log_merger.h b/src/software/logger/log_merger.h index 00a44c02de..2cdbd9d679 100644 --- a/src/software/logger/log_merger.h +++ b/src/software/logger/log_merger.h @@ -5,8 +5,6 @@ #include #include -#include "compat_flags.h" - /** * Handles merging repeated log messages into a single message @@ -43,9 +41,11 @@ class LogMerger * Looks through the message list for expired messages, removes them from the list and * map, and returns them as strings */ - std::list _getOldMessages(Clock::time_point current_time); + std::list _getOldMessages( + std::chrono::_V2::system_clock::time_point current_time); - const Clock::duration LOG_MERGE_DURATION = std::chrono::seconds(2); + const std::chrono::_V2::system_clock::duration LOG_MERGE_DURATION = + std::chrono::seconds(2); private: /** @@ -55,9 +55,10 @@ class LogMerger { g3::LogMessage log; std::string msg; - Clock::time_point timestamp; + std::chrono::_V2::system_clock::time_point timestamp; - Message(g3::LogMessage &log, std::string msg, Clock::time_point timestamp) + Message(g3::LogMessage &log, std::string msg, + std::chrono::_V2::system_clock::time_point timestamp) : log(log), msg(msg), timestamp(timestamp) { } @@ -67,7 +68,8 @@ class LogMerger repeat_map; // maps string messages to their number of repeats for fast access std::list message_list; // used to keep track of time order for messages - Clock::duration passed_time; // for testing, time passed manually + std::chrono::_V2::system_clock::duration + passed_time; // for testing, time passed manually bool enable_merging; }; diff --git a/src/software/logger/logger.h b/src/software/logger/logger.h index df6691750e..d71e6a6a48 100644 --- a/src/software/logger/logger.h +++ b/src/software/logger/logger.h @@ -3,11 +3,12 @@ #include #include +#include +#include #include #include #include -#include "compat_flags.h" #include "software/logger/coloured_cout_sink.h" #include "software/logger/csv_sink.h" #include "software/logger/custom_logging_levels.h" @@ -90,9 +91,9 @@ class LoggerSingleton // hermetic build principles // if log dir doesn't exist, create it - if (!fs::exists(runtime_dir)) + if (!std::experimental::filesystem::exists(runtime_dir)) { - fs::create_directories(runtime_dir); + std::experimental::filesystem::create_directories(runtime_dir); } auto csv_sink_handle = logWorker->addSink(std::make_unique(runtime_dir), diff --git a/src/software/logger/proto_logger.cpp b/src/software/logger/proto_logger.cpp index 4cde56cab7..2362267139 100644 --- a/src/software/logger/proto_logger.cpp +++ b/src/software/logger/proto_logger.cpp @@ -5,13 +5,13 @@ #include #include +#include #include #include #include #include #include "base64.h" -#include "compat_flags.h" #include "shared/constants.h" ProtoLogger::ProtoLogger(const std::string& log_path, @@ -31,7 +31,7 @@ ProtoLogger::ProtoLogger(const std::string& log_path, std::stringstream ss; ss << std::put_time(&tm, REPLAY_FILE_TIME_FORMAT.data()); log_folder_ = log_path_ + "/" + REPLAY_FILE_PREFIX + ss.str() + "/"; - fs::create_directories(log_folder_); + std::experimental::filesystem::create_directories(log_folder_); // Start logging in a separate thread log_thread_ = std::thread(&ProtoLogger::logProtobufs, this); diff --git a/src/software/sensor_fusion/filter/ball_filter.cpp b/src/software/sensor_fusion/filter/ball_filter.cpp index 0967a76ff8..1bee4930f4 100644 --- a/src/software/sensor_fusion/filter/ball_filter.cpp +++ b/src/software/sensor_fusion/filter/ball_filter.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include "shared/constants.h" @@ -263,9 +264,17 @@ BallFilter::LinearRegressionResults BallFilter::calculateLinearRegression( A.bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b); // How to calculate the error is from // https://eigen.tuxfamily.org/dox/group__TutorialLinearAlgebra.html - // NOTE: using absolute error instead of relative because coordinates - // values should not affect error, also handles divide by 0 error - double regression_error = (A * regression_vector - b).norm(); // norm() is L2 norm + double regression_error = std::numeric_limits::max(); + + if ((A * regression_vector - b).norm() == 0 && b.norm() == 0) + { + regression_error = 0; + } + if (b.norm() != 0) + { + regression_error = + (A * regression_vector - b).norm() / (b.norm()); // norm() is L2 norm + } // Find 2 points on the regression line that we solved for, and use this to construct // our own Line class diff --git a/src/software/sensor_fusion/filter/ball_filter.h b/src/software/sensor_fusion/filter/ball_filter.h index cf108b2ac6..678bef7ae3 100644 --- a/src/software/sensor_fusion/filter/ball_filter.h +++ b/src/software/sensor_fusion/filter/ball_filter.h @@ -43,8 +43,7 @@ class BallFilter static constexpr double MAX_BUFFER_SIZE_VELOCITY_MAGNITUDE = 4.0; // The extra amount beyond the ball's max speed that we treat ball detections as valid static constexpr double MAX_ACCEPTABLE_BALL_SPEED_BUFFER = 2.0; - // The maximum root mean squared error threshold to considering using the generated - // linear regression. + // The maximum error threshold to considering using the generated linear regression // TODO (#2752): Investigate different values of error threshold static constexpr double LINEAR_REGRESSION_ERROR_THRESHOLD = 1000.0; @@ -86,7 +85,6 @@ class BallFilter struct LinearRegressionResults { Line regression_line; - // Regression error is root mean squared error double regression_error; }; @@ -133,8 +131,7 @@ class BallFilter /** * Given a buffer of ball detections, returns the line of best fit through - * the detection positions, and calculate the root mean squared error of this - * regression. + * the detection positions, and calculate the error of this regression. * Note: also considers vertical lines. * * @throws std::invalid_argument if ball_detections has less than 2 elements @@ -148,8 +145,7 @@ class BallFilter /** * Given a list of ball detections, use linear regression to find a line of best fit - * through the ball positions, and calculate the root mean squared error of this - * regression. + * through the ball positions, and calculate the error of this regression. * * @throws std::invalid_argument if ball_detections has less than 2 elements * diff --git a/src/software/simulated_tests/simulated_test_fixture.py b/src/software/simulated_tests/simulated_test_fixture.py index ad2d319dbb..10819a1a00 100644 --- a/src/software/simulated_tests/simulated_test_fixture.py +++ b/src/software/simulated_tests/simulated_test_fixture.py @@ -520,14 +520,12 @@ def simulated_test_runner(): args.debug_simulator, args.enable_realism, ) as simulator, FullSystem( - "software/unix_full_system", f"{args.blue_full_system_runtime_dir}/test/{test_name}", args.debug_blue_full_system, False, should_restart_on_crash=False, running_in_realtime=args.enable_thunderscope, ) as blue_fs, FullSystem( - "software/unix_full_system", f"{args.yellow_full_system_runtime_dir}/test/{test_name}", args.debug_yellow_full_system, True, diff --git a/src/software/thunderscope/BUILD b/src/software/thunderscope/BUILD index 2e7fddb8de..464997efca 100644 --- a/src/software/thunderscope/BUILD +++ b/src/software/thunderscope/BUILD @@ -6,7 +6,6 @@ package(default_visibility = ["//visibility:public"]) compile_pip_requirements( name = "requirements", src = "requirements.in", - requirements_darwin = "requirements_lock.darwin.txt", requirements_txt = "requirements_lock.txt", ) @@ -21,7 +20,6 @@ py_binary( ":util", "//software/thunderscope/binary_context_managers:full_system", "//software/thunderscope/binary_context_managers:game_controller", - "//software/thunderscope/binary_context_managers:runtime_manager", "//software/thunderscope/binary_context_managers:simulator", ], ) diff --git a/src/software/thunderscope/binary_context_managers/BUILD b/src/software/thunderscope/binary_context_managers/BUILD index 0fe462dd43..e7805dad6f 100644 --- a/src/software/thunderscope/binary_context_managers/BUILD +++ b/src/software/thunderscope/binary_context_managers/BUILD @@ -22,7 +22,6 @@ py_library( deps = [ "//proto:import_all_protos", "//software/networking:ssl_proto_communication", - "//software/thunderscope:util", "//software/thunderscope/common:thread_safe_circular_buffer", ], ) @@ -51,12 +50,3 @@ py_library( "//software/thunderscope:time_provider", ], ) - -py_library( - name = "runtime_manager", - srcs = [ - "runtime_installer.py", - "runtime_loader.py", - "runtime_manager.py", - ], -) diff --git a/src/software/thunderscope/binary_context_managers/full_system.py b/src/software/thunderscope/binary_context_managers/full_system.py index 8e8abc4e48..5fc0bad397 100644 --- a/src/software/thunderscope/binary_context_managers/full_system.py +++ b/src/software/thunderscope/binary_context_managers/full_system.py @@ -1,20 +1,18 @@ from __future__ import annotations -import logging import os -import threading +import logging import time -from subprocess import Popen, TimeoutExpired +import threading -from software.py_constants import * +from subprocess import Popen, TimeoutExpired +from software.thunderscope.gl.layers.gl_obstacle_layer import ObstacleList +from software.thunderscope.proto_unix_io import ProtoUnixIO from software.python_bindings import * - from proto.import_all_protos import * from software.py_constants import * from software.thunderscope.constants import LogLevels from software.thunderscope.binary_context_managers.util import is_cmd_running -from software.thunderscope.gl.layers.gl_obstacle_layer import ObstacleList -from software.thunderscope.proto_unix_io import ProtoUnixIO class FullSystem: @@ -22,7 +20,6 @@ class FullSystem: def __init__( self, - path_to_binary: str, full_system_runtime_dir: os.PathLike = None, debug_full_system: bool = False, friendly_colour_yellow: bool = False, @@ -33,7 +30,6 @@ def __init__( ) -> None: """Run FullSystem - :param path_to_binary: The path of the binary used for this unix full system :param full_system_runtime_dir: The directory to run the blue full_system in :param debug_full_system: Whether to run the full_system in debug mode :param friendly_color_yellow: a argument passed into the unix_full_system binary (--friendly_colour_yellow) @@ -42,7 +38,6 @@ def __init__( :param running_in_realtime: True if we are running fullsystem in realtime, else False :param log_level: Minimum g3log level that will be printed (DEBUG|INFO|WARNING|FATAL) """ - self.path_to_binary = path_to_binary self.full_system_runtime_dir = full_system_runtime_dir self.debug_full_system = debug_full_system self.friendly_colour_yellow = friendly_colour_yellow @@ -51,6 +46,7 @@ def __init__( self.should_run_under_sudo = run_sudo self.running_in_realtime = running_in_realtime self.log_level = log_level + self.thread = threading.Thread(target=self.__restart__, daemon=True) def __enter__(self) -> FullSystem: @@ -69,12 +65,13 @@ def __enter__(self) -> FullSystem: except: pass - self.full_system = "{} --runtime_dir={} {} {} --log_level={}".format( - self.path_to_binary, - self.full_system_runtime_dir, - "--friendly_colour_yellow" if self.friendly_colour_yellow else "", - "--ci" if not self.running_in_realtime else "", - self.log_level.value, + self.full_system = ( + "software/unix_full_system --runtime_dir={} {} {} --log_level={}".format( + self.full_system_runtime_dir, + "--friendly_colour_yellow" if self.friendly_colour_yellow else "", + "--ci" if not self.running_in_realtime else "", + self.log_level.value, + ) ) if self.should_run_under_sudo: diff --git a/src/software/thunderscope/binary_context_managers/game_controller.py b/src/software/thunderscope/binary_context_managers/game_controller.py index 79cc8a0925..20c8f11033 100644 --- a/src/software/thunderscope/binary_context_managers/game_controller.py +++ b/src/software/thunderscope/binary_context_managers/game_controller.py @@ -4,6 +4,7 @@ import random import logging import os +import socket import time from subprocess import Popen from typing import Any @@ -20,7 +21,6 @@ from software.thunderscope.common.thread_safe_circular_buffer import ( ThreadSafeCircularBuffer, ) -from software.thunderscope.util import is_current_platform_macos logger = logging.getLogger(__name__) import itertools @@ -288,15 +288,10 @@ def __send_referee_command(data: Referee) -> None: if autoref_proto_unix_io is not None: autoref_proto_unix_io.send_proto(Referee, data) - if is_current_platform_macos(): - loopback_iface = "en0" - else: - loopback_iface = "lo" - self.receive_referee_command = tbots_cpp.SSLRefereeProtoListener( Gamecontroller.REFEREE_IP, self.referee_port, - loopback_iface, + "lo", __send_referee_command, True, ) diff --git a/src/software/thunderscope/binary_context_managers/runtime_installer.py b/src/software/thunderscope/binary_context_managers/runtime_installer.py deleted file mode 100644 index 952eb80613..0000000000 --- a/src/software/thunderscope/binary_context_managers/runtime_installer.py +++ /dev/null @@ -1,67 +0,0 @@ -import requests -from pathlib import Path -import tarfile -import shutil -import platform -from software.thunderscope.constants import RuntimeManagerConstants - - -class RuntimeInstaller: - """Delegate class for handling runtime installation and remote interfacing""" - - def __init__(self): - self.runtime_install_targets = {} - - def fetch_remote_runtimes(self) -> list[str]: - """Requests a list of available runtimes from the remote. This is an expensive operation - and should only be called when necessary. - :return: A unique list of names for available runtimes - """ - releases = requests.get( - RuntimeManagerConstants.RELEASES_URL, - headers={"Accept": "application/vnd.github+json"}, - ).json() - - version_names = [] - - # Currently the only targets that are supported for each os - os_to_target = {"Darwin": "mac-arm64", "Linux": "ubuntu-x86"} - target = os_to_target[platform.system()] - - for release in releases: - version = release["tag_name"] - for asset in release.get("assets", []): - url = asset["browser_download_url"] - - if "unix_full_system" in url and target in url: - version_names.append(version) - self.runtime_install_targets[version] = url - - return version_names[: RuntimeManagerConstants.MAX_RELEASES_FETCHED] - - def install_runtime(self, version: str) -> None: - """Installs the runtime of the specified version or throws an error upon failure. - Ensures that the runtime is compatible with the current platform - :param version: Version of the runtime hosted on the remote to install - """ - url = self.runtime_install_targets[version] - - filename = Path(url).name - target_dir = Path(RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH) - tmp_dir = Path("/tmp") - tmp_path = tmp_dir / filename - extracted_binary_name = "unix_full_system" - - with requests.get(url, stream=True) as r: - r.raise_for_status() - with open(tmp_path, "wb") as f: - for chunk in r.iter_content(chunk_size=8192): - if chunk: - f.write(chunk) - - dest = target_dir / f"{extracted_binary_name}_{version}" - - # Our release assets for FullSystem are always tar.gz files - with tarfile.open(tmp_path, "r:*") as tar: - tar.extractall(tmp_dir) - shutil.move(tmp_dir / extracted_binary_name, dest) diff --git a/src/software/thunderscope/binary_context_managers/runtime_loader.py b/src/software/thunderscope/binary_context_managers/runtime_loader.py deleted file mode 100644 index 49208a7b17..0000000000 --- a/src/software/thunderscope/binary_context_managers/runtime_loader.py +++ /dev/null @@ -1,139 +0,0 @@ -from tomllib import TOMLDecodeError -from software.thunderscope.constants import RuntimeManagerConstants -import os -import tomllib -import logging - - -class RuntimeConfig: - """Class to store the names and get paths of the two binaries""" - - def __init__( - self, - blue_runtime: str | None = None, - yellow_runtime: str | None = None, - ) -> None: - """Create runtime config, replacing invalid runtimes with default FullSystem - :param blue_runtime: blue runtime name, None if default - :param yellow_runtime: yellow runtime name, None if default - """ - self.blue_runtime = ( - blue_runtime - if blue_runtime - and self._is_valid_runtime_path(self._get_runtime_path(blue_runtime)) - else RuntimeManagerConstants.DEFAULT_BINARY_NAME - ) - self.yellow_runtime = ( - yellow_runtime - if yellow_runtime - and self._is_valid_runtime_path(self._get_runtime_path(yellow_runtime)) - else RuntimeManagerConstants.DEFAULT_BINARY_NAME - ) - - def get_blue_runtime_path(self) -> str: - """Returns the path of the stored yellow runtime - :return: the absolute path of the binary as a string, or the relative path of our FullSystem - """ - return self._get_runtime_path(self.blue_runtime) - - def get_yellow_runtime_path(self) -> str: - """Returns the path of the stored yellow runtime - :return: the absolute path of the binary as a string, or the relative path of our FullSystem - """ - return self._get_runtime_path(self.yellow_runtime) - - def _get_runtime_path(self, selected_runtime: str) -> str: - """Gets the absolute path of a binary given its name, or the path of our default FullSystem - if the binary is not valid. - :param selected_runtime: the name of the selected runtime binary - :return: the absolute path of the binary as a string, or the relative path of our FullSystem - """ - file_path = os.path.join( - RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH, selected_runtime - ) - # Default to local FullSystem if it is selected or the selected binary isn't a valid runtime - if ( - selected_runtime == RuntimeManagerConstants.DEFAULT_BINARY_NAME - or not self._is_valid_runtime_path(file_path) - ): - return RuntimeManagerConstants.DEFAULT_BINARY_PATH - # Remove leading and trailing white space and return - return file_path.strip() - - def _is_valid_runtime_path(self, runtime_path: str) -> bool: - """Returns if the binary exists and if it is an executable. - :param runtime_path: the path to check - :return: True if it is a valid runtime - """ - return os.path.isfile(runtime_path) and os.access(runtime_path, os.X_OK) - - -class RuntimeLoader: - """Delegate class for handling local runtimes and managing runtime selection""" - - def fetch_installed_runtimes(self) -> list[str]: - """Fetches the list of installed runtimes from the local disk. - Creates the external runtimes directory in our local disk if it does not exist yet. - :return: A list of installed runtime names - """ - runtime_list = [RuntimeManagerConstants.DEFAULT_BINARY_NAME] - - if not os.path.isdir(RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH): - os.mkdir(RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH) - - # Check for all executable files in the directory, and add its name to the list - for file_name in os.listdir(RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH): - file_path = os.path.join( - RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH, file_name - ) - if os.access(file_path, os.X_OK): - runtime_list.append(file_name) - - return runtime_list - - def load_selected_runtimes(self, yellow_runtime: str, blue_runtime: str) -> None: - """Loads the yellow and blue runtimes specified by saving them in the local disk. - :param blue_runtime: name of the blue runtime to set - :param yellow_runtime: name of the yellow runtime to set - """ - # Format as TOML - selected_runtimes = ( - f'{RuntimeManagerConstants.RUNTIME_CONFIG_BLUE_KEY} = "{blue_runtime}"\n' - f'{RuntimeManagerConstants.RUNTIME_CONFIG_YELLOW_KEY} = "{yellow_runtime}"' - ) - - # create a new config file if it doesn't exist, and write in the format above to it - with open(RuntimeManagerConstants.RUNTIME_CONFIG_PATH, "w") as file: - file.write(selected_runtimes) - - def fetch_runtime_config(self) -> RuntimeConfig: - """Fetches the runtime configuration from the local disk, creating it if it doesn't exist. - :return: Returns the runtime configuration as a RuntimeConfig - """ - # Create empty config file if doesn't exist yet - os.makedirs( - os.path.dirname(RuntimeManagerConstants.RUNTIME_CONFIG_PATH), exist_ok=True - ) - open(RuntimeManagerConstants.RUNTIME_CONFIG_PATH, "a").close() - - try: - with open(RuntimeManagerConstants.RUNTIME_CONFIG_PATH, "rb") as file: - selected_runtime_dict = tomllib.load(file) - - # Get the persisted runtimes - config = RuntimeConfig( - selected_runtime_dict.get( - RuntimeManagerConstants.RUNTIME_CONFIG_BLUE_KEY - ), - selected_runtime_dict.get( - RuntimeManagerConstants.RUNTIME_CONFIG_YELLOW_KEY - ), - ) - - return config - except TOMLDecodeError: - logging.warning( - f"Failed to read TOML file at: {RuntimeManagerConstants.RUNTIME_CONFIG_PATH}" - ) - - return RuntimeConfig() diff --git a/src/software/thunderscope/binary_context_managers/runtime_manager.py b/src/software/thunderscope/binary_context_managers/runtime_manager.py deleted file mode 100644 index 3316ec0372..0000000000 --- a/src/software/thunderscope/binary_context_managers/runtime_manager.py +++ /dev/null @@ -1,49 +0,0 @@ -from software.thunderscope.binary_context_managers.runtime_installer import ( - RuntimeInstaller, -) -from software.thunderscope.binary_context_managers.runtime_loader import ( - RuntimeLoader, - RuntimeConfig, -) - - -class RuntimeManager: - """Class for interfacing with AI runtimes/backends (full system or external) on the disk""" - - def __init__(self): - self.runtime_installer = RuntimeInstaller() - self.runtime_loader = RuntimeLoader() - - def fetch_remote_runtimes(self) -> list[str]: - """Requests a list of available runtimes from the remote. Includes DEFAULT_BINARY_NAME by default - :return: A unique list of names for available runtimes - """ - return self.runtime_installer.fetch_remote_runtimes() - - def install_runtime(self, version: str) -> None: - """Installs the runtime of the specified version or throws an error upon failure - :param version: Version of the runtime hosted on the remote to install - """ - self.runtime_installer.install_runtime(version) - - def fetch_installed_runtimes(self) -> list[str]: - """Fetches the list of available runtimes from the local disk - :return: A list of names for available runtimes - """ - return self.runtime_loader.fetch_installed_runtimes() - - def load_selected_runtimes(self, yellow_runtime: str, blue_runtime: str) -> None: - """Loads the runtimes into the runtime loader config file on the local disk - :param blue_runtime: name of the blue runtime to load - :param yellow_runtime: name of the yellow runtime to load - """ - self.runtime_loader.load_selected_runtimes(yellow_runtime, blue_runtime) - - def fetch_runtime_config(self) -> RuntimeConfig: - """Fetches the runtime configuration from the local disk - :return: Returns the runtime configuration as a RuntimeConfig - """ - return self.runtime_loader.fetch_runtime_config() - - -runtime_manager_instance = RuntimeManager() diff --git a/src/software/thunderscope/constants.py b/src/software/thunderscope/constants.py index 4ba2612e56..12cb5b0d83 100644 --- a/src/software/thunderscope/constants.py +++ b/src/software/thunderscope/constants.py @@ -393,17 +393,3 @@ class LogLevels(StrEnum): INFO = "INFO" WARNING = "WARNING" FATAL = "FATAL" - - -class RuntimeManagerConstants: - """Constants for Runtime Manager""" - - RUNTIME_CONFIG_BLUE_KEY = "blue_runtime_name" - RUNTIME_CONFIG_YELLOW_KEY = "yellow_runtime_name" - DEFAULT_BINARY_PATH = "software/unix_full_system" - DEFAULT_BINARY_NAME = "Current Fullsystem" - EXTERNAL_RUNTIMES_PATH = "/opt/tbotspython/external_runtimes" - RUNTIME_CONFIG_PATH = f"{EXTERNAL_RUNTIMES_PATH}/runtime_config.toml" - RELEASES_URL = "https://api.github.com/repos/UBC-Thunderbots/Software/releases" - DOWNLOAD_URL = "https://github.com/UBC-Thunderbots/Software/releases/download/" - MAX_RELEASES_FETCHED = 5 diff --git a/src/software/thunderscope/gl/widgets/BUILD b/src/software/thunderscope/gl/widgets/BUILD index 1033dacfe6..7526278ced 100644 --- a/src/software/thunderscope/gl/widgets/BUILD +++ b/src/software/thunderscope/gl/widgets/BUILD @@ -2,24 +2,6 @@ load("@thunderscope_deps//:requirements.bzl", "requirement") package(default_visibility = ["//visibility:public"]) -py_library( - name = "gl_runtime_installer", - srcs = ["gl_runtime_installer.py"], - deps = [ - "//software/thunderscope/binary_context_managers:runtime_manager", - requirement("pyqtgraph"), - ], -) - -py_library( - name = "gl_runtime_selector", - srcs = ["gl_runtime_selector.py"], - deps = [ - "//software/thunderscope/binary_context_managers:runtime_manager", - requirement("pyqtgraph"), - ], -) - py_library( name = "gl_toolbar", srcs = ["gl_toolbar.py"], @@ -51,8 +33,6 @@ py_library( name = "gl_gamecontroller_toolbar", srcs = ["gl_gamecontroller_toolbar.py"], deps = [ - ":gl_runtime_installer", - ":gl_runtime_selector", ":gl_toolbar", "//proto:import_all_protos", "//software/networking:ssl_proto_communication", diff --git a/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py b/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py index d1aed796fd..042b6622e8 100644 --- a/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py @@ -4,12 +4,8 @@ from proto.ssl_gc_common_pb2 import Team as SslTeam from typing import Callable, override import webbrowser -from software.thunderscope.gl.widgets.gl_runtime_selector import GLRuntimeSelectorDialog from software.thunderscope.gl.widgets.gl_toolbar import GLToolbar from software.thunderscope.proto_unix_io import ProtoUnixIO -from software.thunderscope.gl.widgets.gl_runtime_installer import ( - GLRuntimeInstallerDialog, -) import qtawesome as qta @@ -99,20 +95,6 @@ def __init__( display_text="Open GC", ) - self.runtime_installer_button = self.__setup_icon_button( - qta.icon("mdi6.download"), - "Opens a runtime installer modal", - self.__open_runtime_installer_dialog, - display_text="Install Runtimes", - ) - - self.runtime_selector_button = self.__setup_icon_button( - qta.icon("mdi6.server"), - "Select runtimes for each team", - self.__open_runtime_selector_dialog, - display_text="Select Runtimes", - ) - # disable the normal start button when no play is selected self.normal_start_enabled = True self.__toggle_normal_start_button() @@ -128,9 +110,6 @@ def __init__( self.__add_separator(self.layout()) self.layout().addWidget(self.gc_browser_button) self.layout().addStretch() - self.__add_separator(self.layout()) - self.layout().addWidget(self.runtime_installer_button) - self.layout().addWidget(self.runtime_selector_button) @override def refresh(self) -> None: @@ -269,17 +248,3 @@ def __send_gc_command(self, command: Command.Type, team: Team) -> None: """ command = ManualGCCommand(manual_command=Command(type=command, for_team=team)) self.proto_unix_io.send_proto(ManualGCCommand, command) - - def __open_runtime_installer_dialog(self) -> None: - """Opens the runtime installer modal, initializing if first time""" - if not hasattr(self, "runtime_installer_dialog"): - self.runtime_installer_dialog = GLRuntimeInstallerDialog( - parent=self.parent() - ) - - self.runtime_installer_dialog.show() - - def __open_runtime_selector_dialog(self) -> None: - """Initializes and opens the runtime selector dialog""" - self.runtime_selector_dialog = GLRuntimeSelectorDialog(parent=self.parent()) - self.runtime_selector_dialog.show() diff --git a/src/software/thunderscope/gl/widgets/gl_runtime_installer.py b/src/software/thunderscope/gl/widgets/gl_runtime_installer.py deleted file mode 100644 index f0facfc5f5..0000000000 --- a/src/software/thunderscope/gl/widgets/gl_runtime_installer.py +++ /dev/null @@ -1,56 +0,0 @@ -from pyqtgraph.Qt.QtWidgets import ( - QDialog, - QWidget, - QPushButton, - QListWidget, - QAbstractItemView, - QVBoxLayout, -) - -from software.thunderscope.binary_context_managers.runtime_manager import ( - runtime_manager_instance, -) - - -class GLRuntimeInstallerDialog(QDialog): - """Modal that displays selectable list of runtimes to install""" - - def __init__(self, parent: QWidget): - """Initializes runtime installer modal, fetching a list of installable - runtimes and adding to a selectable list - - :param parent: the modal's parent - """ - super().__init__(parent) - - runtimes = runtime_manager_instance.fetch_remote_runtimes() - - self.setWindowTitle("Install runtimes") - self.setModal(True) - self.setMinimumWidth(400) - - install_button = QPushButton("Install") - install_button.clicked.connect(self.__install_selected_runtimes) - - runtime_select_list = QListWidget() - runtime_select_list.setSelectionMode( - QAbstractItemView.SelectionMode.MultiSelection - ) - runtime_select_list.setFixedHeight(200) - runtime_select_list.addItems(runtimes) - - self.runtime_select_list = runtime_select_list - - layout = QVBoxLayout(self) - layout.addWidget(runtime_select_list) - layout.addWidget(install_button) - - def __install_selected_runtimes(self) -> None: - """Installs all runtimes that are currently selected""" - selected_items = self.runtime_select_list.selectedItems() - selected_runtimes = [item.text() for item in selected_items] - - for runtime in selected_runtimes: - runtime_manager_instance.install_runtime(runtime) - - self.close() diff --git a/src/software/thunderscope/gl/widgets/gl_runtime_selector.py b/src/software/thunderscope/gl/widgets/gl_runtime_selector.py deleted file mode 100644 index 1f84f3d835..0000000000 --- a/src/software/thunderscope/gl/widgets/gl_runtime_selector.py +++ /dev/null @@ -1,100 +0,0 @@ -from pyqtgraph.Qt.QtWidgets import ( - QDialog, - QWidget, - QVBoxLayout, - QHBoxLayout, - QLabel, - QPushButton, - QComboBox, -) - -from software.thunderscope.binary_context_managers.runtime_manager import ( - runtime_manager_instance, -) - - -class GLRuntimeSelectorDialog(QDialog): - """Modal that displays the selectable list of runtimes for blue and yellow teams""" - - def __init__(self, parent: QWidget): - """Initializes the runtime selector modal, displaying the same list of installed - runtimes for both the blue and yellow teams. - - :param parent: the modal's parent - """ - super().__init__(parent) - - runtime_options = runtime_manager_instance.fetch_installed_runtimes() - runtime_config = runtime_manager_instance.fetch_runtime_config() - - # Put selected runtimes from config at start of list - blue_runtimes = [runtime_config.blue_runtime] + [ - x for x in runtime_options if x != runtime_config.blue_runtime - ] - yellow_runtimes = [runtime_config.yellow_runtime] + [ - x for x in runtime_options if x != runtime_config.yellow_runtime - ] - - self.setWindowTitle("Select Runtimes") - self.setModal(True) - self.setMinimumWidth(400) - - layout = QVBoxLayout(self) - - # Blue runtime - layout.addWidget(QLabel("Blue Runtime")) - self.blue_menu = QComboBox() - self.blue_menu.addItems(blue_runtimes) - self.blue_menu.currentTextChanged.connect(self._on_blue_changed) - self._blue_selection = self.blue_menu.currentText() - layout.addWidget(self.blue_menu) - - layout.addSpacing(10) - - # Yellow runtime - layout.addWidget(QLabel("Yellow Runtime")) - self.yellow_menu = QComboBox() - self.yellow_menu.addItems(yellow_runtimes) - self.yellow_menu.currentTextChanged.connect(self._on_yellow_changed) - self._yellow_selection = self.yellow_menu.currentText() - layout.addWidget(self.yellow_menu) - - # Restart note - layout.addSpacing(15) - restart_note = QLabel( - "Note: Restart Thunderscope for changes to take effect." - ) - layout.addWidget(restart_note) - - # Done button - layout.addSpacing(15) - button_row = QHBoxLayout() - button_row.addStretch() - - done_button = QPushButton("Done") - done_button.clicked.connect(self._on_done) - button_row.addWidget(done_button) - - layout.addLayout(button_row) - - def _on_blue_changed(self, value: str) -> None: - """Stores currently selected runtime for blue team - - :param value: the value of the selected option - """ - self._blue_selection = value - - def _on_yellow_changed(self, value: str) -> None: - """Stores currently selected runtime for yellow team - - :param value: the value of the selected option - """ - self._yellow_selection = value - - def _on_done(self) -> None: - """Commits the selected runtimes and closes the modal.""" - runtime_manager_instance.load_selected_runtimes( - self._yellow_selection, self._blue_selection - ) - - self.close() diff --git a/src/software/thunderscope/requirements.in b/src/software/thunderscope/requirements.in index 18c98bc70f..32a0054344 100644 --- a/src/software/thunderscope/requirements.in +++ b/src/software/thunderscope/requirements.in @@ -1,6 +1,6 @@ colorama==0.4.6 netifaces==0.11.0 -evdev==1.7.0; sys_platform == "linux" +evdev==1.7.0 numpy==1.26.4 protobuf==6.31.1 pyqtgraph==0.13.7 diff --git a/src/software/thunderscope/requirements_lock.darwin.txt b/src/software/thunderscope/requirements_lock.darwin.txt deleted file mode 100644 index 87532ad2ba..0000000000 --- a/src/software/thunderscope/requirements_lock.darwin.txt +++ /dev/null @@ -1,126 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# bazel run //software/thunderscope:requirements.update -# -colorama==0.4.6 \ - --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ - --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 - # via -r software/thunderscope/requirements.in -darkdetect==0.7.1 \ - --hash=sha256:3efe69f8ecd5f1b7f4fbb0d1d93f656b0e493c45cc49222380ffe2a529cbc866 \ - --hash=sha256:47be3cf5134432ddb616bbffc927237718407914993c82809983e7ccebf49013 - # via pyqtdarktheme-fork -netifaces==0.11.0 \ - --hash=sha256:043a79146eb2907edf439899f262b3dfe41717d34124298ed281139a8b93ca32 \ - --hash=sha256:08e3f102a59f9eaef70948340aeb6c89bd09734e0dca0f3b82720305729f63ea \ - --hash=sha256:0f6133ac02521270d9f7c490f0c8c60638ff4aec8338efeff10a1b51506abe85 \ - --hash=sha256:18917fbbdcb2d4f897153c5ddbb56b31fa6dd7c3fa9608b7e3c3a663df8206b5 \ - --hash=sha256:2479bb4bb50968089a7c045f24d120f37026d7e802ec134c4490eae994c729b5 \ - --hash=sha256:2650beee182fed66617e18474b943e72e52f10a24dc8cac1db36c41ee9c041b7 \ - --hash=sha256:28f4bf3a1361ab3ed93c5ef360c8b7d4a4ae060176a3529e72e5e4ffc4afd8b0 \ - --hash=sha256:3ecb3f37c31d5d51d2a4d935cfa81c9bc956687c6f5237021b36d6fdc2815b2c \ - --hash=sha256:469fc61034f3daf095e02f9f1bbac07927b826c76b745207287bc594884cfd05 \ - --hash=sha256:48324183af7f1bc44f5f197f3dad54a809ad1ef0c78baee2c88f16a5de02c4c9 \ - --hash=sha256:50721858c935a76b83dd0dd1ab472cad0a3ef540a1408057624604002fcfb45b \ - --hash=sha256:54ff6624eb95b8a07e79aa8817288659af174e954cca24cdb0daeeddfc03c4ff \ - --hash=sha256:5be83986100ed1fdfa78f11ccff9e4757297735ac17391b95e17e74335c2047d \ - --hash=sha256:5f9ca13babe4d845e400921973f6165a4c2f9f3379c7abfc7478160e25d196a4 \ - --hash=sha256:73ff21559675150d31deea8f1f8d7e9a9a7e4688732a94d71327082f517fc6b4 \ - --hash=sha256:7dbb71ea26d304e78ccccf6faccef71bb27ea35e259fb883cfd7fd7b4f17ecb1 \ - --hash=sha256:815eafdf8b8f2e61370afc6add6194bd5a7252ae44c667e96c4c1ecf418811e4 \ - --hash=sha256:841aa21110a20dc1621e3dd9f922c64ca64dd1eb213c47267a2c324d823f6c8f \ - --hash=sha256:84e4d2e6973eccc52778735befc01638498781ce0e39aa2044ccfd2385c03246 \ - --hash=sha256:8f7da24eab0d4184715d96208b38d373fd15c37b0dafb74756c638bd619ba150 \ - --hash=sha256:96c0fe9696398253f93482c84814f0e7290eee0bfec11563bd07d80d701280c3 \ - --hash=sha256:aab1dbfdc55086c789f0eb37affccf47b895b98d490738b81f3b2360100426be \ - --hash=sha256:c03fb2d4ef4e393f2e6ffc6376410a22a3544f164b336b3a355226653e5efd89 \ - --hash=sha256:c37a1ca83825bc6f54dddf5277e9c65dec2f1b4d0ba44b8fd42bc30c91aa6ea1 \ - --hash=sha256:c92ff9ac7c2282009fe0dcb67ee3cd17978cffbe0c8f4b471c00fe4325c9b4d4 \ - --hash=sha256:c9a3a47cd3aaeb71e93e681d9816c56406ed755b9442e981b07e3618fb71d2ac \ - --hash=sha256:cb925e1ca024d6f9b4f9b01d83215fd00fe69d095d0255ff3f64bffda74025c8 \ - --hash=sha256:d07b01c51b0b6ceb0f09fc48ec58debd99d2c8430b09e56651addeaf5de48048 \ - --hash=sha256:e76c7f351e0444721e85f975ae92718e21c1f361bda946d60a214061de1f00a1 \ - --hash=sha256:eb4813b77d5df99903af4757ce980a98c4d702bbcb81f32a0b305a1537bdf0b1 - # via -r software/thunderscope/requirements.in -numpy==1.26.4 \ - --hash=sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b \ - --hash=sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818 \ - --hash=sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20 \ - --hash=sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0 \ - --hash=sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010 \ - --hash=sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a \ - --hash=sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea \ - --hash=sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c \ - --hash=sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71 \ - --hash=sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110 \ - --hash=sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be \ - --hash=sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a \ - --hash=sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a \ - --hash=sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5 \ - --hash=sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed \ - --hash=sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd \ - --hash=sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c \ - --hash=sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e \ - --hash=sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0 \ - --hash=sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c \ - --hash=sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a \ - --hash=sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b \ - --hash=sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0 \ - --hash=sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6 \ - --hash=sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2 \ - --hash=sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a \ - --hash=sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30 \ - --hash=sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218 \ - --hash=sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5 \ - --hash=sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07 \ - --hash=sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2 \ - --hash=sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4 \ - --hash=sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764 \ - --hash=sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef \ - --hash=sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3 \ - --hash=sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f - # via - # -r software/thunderscope/requirements.in - # pyqtgraph -packaging==24.2 \ - --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ - --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f - # via qtpy -protobuf==6.31.1 \ - --hash=sha256:0414e3aa5a5f3ff423828e1e6a6e907d6c65c1d5b7e6e975793d5590bdeecc16 \ - --hash=sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447 \ - --hash=sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6 \ - --hash=sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402 \ - --hash=sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e \ - --hash=sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9 \ - --hash=sha256:8764cf4587791e7564051b35524b72844f845ad0bb011704c3736cce762d8fe9 \ - --hash=sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39 \ - --hash=sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a - # via -r software/thunderscope/requirements.in -pyqt-toast-notification==1.3.2 \ - --hash=sha256:135736ec0f16bff41104dee3c60ac318e5d55ae3378bf26892c6d08c36088ae6 \ - --hash=sha256:82688101202737736d51ab6c74a573b32266ecb7c8b0002f913407bd369737d9 - # via -r software/thunderscope/requirements.in -pyqt6-qt6==6.8.1 \ - --hash=sha256:006d786693d0511fbcf184a862edbd339c6ed1bb3bd9de363d73a19ed4b23dff \ - --hash=sha256:08065d595f1e6fc2dde9f4450eeff89082f4bad26f600a8e9b9cc5966716bfcf \ - --hash=sha256:1eb8460a1fdb38d0b2458c2974c01d471c1e59e4eb19ea63fc447aaba3ad530e \ - --hash=sha256:20843cb86bd94942d1cd99e39bf1aeabb875b241a35a8ab273e4bbbfa63776db \ - --hash=sha256:9f3790c4ce4dc576e48b8718d55fb8743057e6cbd53a6ca1dd253ffbac9b7287 \ - --hash=sha256:a8bc2ed4ee5e7c6ff4dd1c7db0b27705d151fee5dc232bbd1bf17618f937f515 \ - --hash=sha256:d6ca5d2b9d2ec0ee4a814b2175f641a5c4299cb80b45e0f5f8356632663f89b3 - # via -r software/thunderscope/requirements.in -pyqtdarktheme-fork==2.3.2 \ - --hash=sha256:3ea94fed5df262d960378409357c63032639f749794d766f41a45ad8558b2523 \ - --hash=sha256:d96ee64f0884678fad9b6bc352d5e37d84ca786fa60ed32ffaa7e6c6bc67e964 - # via -r software/thunderscope/requirements.in -pyqtgraph==0.13.7 \ - --hash=sha256:64f84f1935c6996d0e09b1ee66fe478a7771e3ca6f3aaa05f00f6e068321d9e3 \ - --hash=sha256:7754edbefb6c367fa0dfb176e2d0610da3ada20aa7a5318516c74af5fb72bf7a - # via -r software/thunderscope/requirements.in -qtpy==2.4.2 \ - --hash=sha256:5a696b1dd7a354cb330657da1d17c20c2190c72d4888ba923f8461da67aa1a1c \ - --hash=sha256:9d6ec91a587cc1495eaebd23130f7619afa5cdd34a277acb87735b4ad7c65156 - # via pyqt-toast-notification diff --git a/src/software/thunderscope/requirements_lock.txt b/src/software/thunderscope/requirements_lock.txt index 1b4d0632ee..a50ed09232 100644 --- a/src/software/thunderscope/requirements_lock.txt +++ b/src/software/thunderscope/requirements_lock.txt @@ -12,7 +12,7 @@ darkdetect==0.7.1 \ --hash=sha256:3efe69f8ecd5f1b7f4fbb0d1d93f656b0e493c45cc49222380ffe2a529cbc866 \ --hash=sha256:47be3cf5134432ddb616bbffc927237718407914993c82809983e7ccebf49013 # via pyqtdarktheme-fork -evdev==1.7.0 ; sys_platform == "linux" \ +evdev==1.7.0 \ --hash=sha256:95bd2a1e0c6ce2cd7a2ecc6e6cd9736ff794b3ad5cb54d81d8cbc2e414d0b870 # via -r software/thunderscope/requirements.in netifaces==0.11.0 \ diff --git a/src/software/thunderscope/robot_diagnostics/BUILD b/src/software/thunderscope/robot_diagnostics/BUILD index aee3a437d1..107b65fce8 100644 --- a/src/software/thunderscope/robot_diagnostics/BUILD +++ b/src/software/thunderscope/robot_diagnostics/BUILD @@ -35,12 +35,8 @@ py_library( ":handheld_controller", "//software/thunderscope:constants", requirement("pyqtgraph"), - ] + select({ - # TODO: remove this selection when we replace evdev to - # other macos supported libs. - "@platforms//os:linux": [requirement("evdev")], - "//conditions:default": [], - }), + requirement("evdev"), + ], ) py_library( diff --git a/src/software/thunderscope/robot_diagnostics/handheld_controller.py b/src/software/thunderscope/robot_diagnostics/handheld_controller.py index d3a39a3ab0..c8e1bc2121 100644 --- a/src/software/thunderscope/robot_diagnostics/handheld_controller.py +++ b/src/software/thunderscope/robot_diagnostics/handheld_controller.py @@ -1,11 +1,6 @@ import numpy -# TODO: remove the try-catch when we rewrite this with macOS-compatible lib -try: - from evdev import InputDevice, ecodes -except ImportError: - pass - +from evdev import InputDevice, ecodes from threading import Thread diff --git a/src/software/thunderscope/robot_diagnostics/handheld_controller_widget.py b/src/software/thunderscope/robot_diagnostics/handheld_controller_widget.py index 12e9464703..ba9473509e 100644 --- a/src/software/thunderscope/robot_diagnostics/handheld_controller_widget.py +++ b/src/software/thunderscope/robot_diagnostics/handheld_controller_widget.py @@ -1,11 +1,7 @@ import numpy -# TODO: remove the try-catch when we rewrite this with macOS-compatible lib -try: - import evdev - from evdev import ecodes -except ImportError: - pass +import evdev +from evdev import ecodes from proto.import_all_protos import * from pyqtgraph.Qt.QtWidgets import * diff --git a/src/software/thunderscope/thunderscope_main.py b/src/software/thunderscope/thunderscope_main.py index 7c764a19a9..59de112b46 100644 --- a/src/software/thunderscope/thunderscope_main.py +++ b/src/software/thunderscope/thunderscope_main.py @@ -8,11 +8,6 @@ import google.protobuf from google.protobuf.internal import api_implementation -from software.thunderscope.binary_context_managers.runtime_manager import ( - runtime_manager_instance, -) - - protobuf_impl_type = api_implementation.Type() assert protobuf_impl_type == "upb", ( f"Trying to use the {protobuf_impl_type} protobuf implementation. " @@ -21,20 +16,16 @@ ) from software.thunderscope.thunderscope import Thunderscope -from software.thunderscope.constants import LogLevels from software.thunderscope.binary_context_managers import * from proto.import_all_protos import * from software.py_constants import * from software.thunderscope.robot_communication import RobotCommunication from software.thunderscope.wifi_communication_manager import WifiCommunicationManager -from software.thunderscope.constants import ( - EstopMode, - ProtoUnixIOTypes, -) +from software.thunderscope.constants import EstopMode, ProtoUnixIOTypes from software.thunderscope.estop_helpers import get_estop_config from software.thunderscope.proto_unix_io import ProtoUnixIO import software.thunderscope.thunderscope_config as config -from software.thunderscope.constants import CI_DURATION_S +from software.thunderscope.constants import CI_DURATION_S, LogLevels from software.thunderscope.util import * from software.thunderscope.binary_context_managers.full_system import FullSystem @@ -43,6 +34,7 @@ from software.thunderscope.binary_context_managers.tigers_autoref import TigersAutoref + ########################################################################### # Thunderscope Main # ########################################################################### @@ -439,14 +431,10 @@ def __ticker(tick_rate_ms: int) -> None: tick_rate_ms, tscope.proto_unix_io_map[ProtoUnixIOTypes.SIM], tscope ) - # Fetch the AI runtime/backends - runtime_config = runtime_manager_instance.fetch_runtime_config() - # Launch all binaries with Simulator( args.simulator_runtime_dir, args.debug_simulator, args.enable_realism ) as simulator, FullSystem( - path_to_binary=runtime_config.get_blue_runtime_path(), full_system_runtime_dir=args.blue_full_system_runtime_dir, debug_full_system=args.debug_blue_full_system, friendly_colour_yellow=False, @@ -455,7 +443,6 @@ def __ticker(tick_rate_ms: int) -> None: running_in_realtime=(not args.ci_mode), log_level=args.log_level, ) as blue_fs, FullSystem( - path_to_binary=runtime_config.get_yellow_runtime_path(), full_system_runtime_dir=args.yellow_full_system_runtime_dir, debug_full_system=args.debug_yellow_full_system, friendly_colour_yellow=True, diff --git a/src/software/thunderscope/util.py b/src/software/thunderscope/util.py index 2e3e420203..d5634557f9 100644 --- a/src/software/thunderscope/util.py +++ b/src/software/thunderscope/util.py @@ -1,4 +1,3 @@ -import platform from typing import Callable, NoReturn, TYPE_CHECKING if TYPE_CHECKING: @@ -196,10 +195,3 @@ def color_from_gradient( int(b_range[i] + (b_range[i + 1] - b_range[i]) * sig_val), int(a_range[i] + (a_range[i + 1] - a_range[i]) * sig_val), ) - - -def is_current_platform_macos() -> bool: - """Return True if the current process is running on macOS. - Uses platform.system(), which should reliably return 'Darwin' on macOS. - """ - return platform.system().lower() == "darwin" From 8a2bf29ff5fdc1d255c7044bb8eb7b1c75ab3edc Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Sat, 7 Feb 2026 12:23:28 -0800 Subject: [PATCH 24/34] fixed syntax --- src/software/simulated_tests/excessive_dribbling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/excessive_dribbling.py index 85f7d75908..c95ef10537 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/excessive_dribbling.py @@ -28,7 +28,7 @@ def get_validation_status( # Use world calculation of dribbling distance, which uses implementation # of initial position of BOT to final position of BALL - if world.hasValue("dribble_displacement"): + if world.HasField("dribble_displacement") and world.dribble_displacement is not None: dribble_disp = world.dribble_displacement dist = tbots_cpp.createSegment(dribble_disp).length() if dist > ( From 57e14e96c114ef4adf2a7c06bd8f5bea226a645a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 7 Feb 2026 20:34:30 +0000 Subject: [PATCH 25/34] [pre-commit.ci lite] apply automatic fixes --- src/software/simulated_tests/excessive_dribbling.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/excessive_dribbling.py index 5d9c42a20f..01c58e9516 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/excessive_dribbling.py @@ -28,7 +28,10 @@ def get_validation_status( # Use world calculation of dribbling distance, which uses implementation # of initial position of bot to final position of BALL - if world.HasField("dribble_displacement") and world.dribble_displacement is not None: + if ( + world.HasField("dribble_displacement") + and world.dribble_displacement is not None + ): dribble_disp = world.dribble_displacement dist = tbots_cpp.createSegment(dribble_disp).length() if dist > ( From 1d98837eedb195b47de83b99a9860b8b815580f3 Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Sat, 7 Feb 2026 12:42:44 -0800 Subject: [PATCH 26/34] i think it's finally good now? --- .../simulated_tests/excessive_dribbling.py | 2 +- .../thunderscope/requirements_lock.txt | 137 ++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/excessive_dribbling.py index 85f7d75908..c95ef10537 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/excessive_dribbling.py @@ -28,7 +28,7 @@ def get_validation_status( # Use world calculation of dribbling distance, which uses implementation # of initial position of BOT to final position of BALL - if world.hasValue("dribble_displacement"): + if world.HasField("dribble_displacement") and world.dribble_displacement is not None: dribble_disp = world.dribble_displacement dist = tbots_cpp.createSegment(dribble_disp).length() if dist > ( diff --git a/src/software/thunderscope/requirements_lock.txt b/src/software/thunderscope/requirements_lock.txt index e69de29bb2..a8731b36d6 100644 --- a/src/software/thunderscope/requirements_lock.txt +++ b/src/software/thunderscope/requirements_lock.txt @@ -0,0 +1,137 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# bazel run //software/thunderscope:requirements.update +# +colorama==0.4.6 \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + # via -r software/thunderscope/requirements.in +darkdetect==0.7.1 \ + --hash=sha256:3efe69f8ecd5f1b7f4fbb0d1d93f656b0e493c45cc49222380ffe2a529cbc866 \ + --hash=sha256:47be3cf5134432ddb616bbffc927237718407914993c82809983e7ccebf49013 + # via pyqtdarktheme-fork +evdev==1.7.0 ; sys_platform == "linux" \ + --hash=sha256:95bd2a1e0c6ce2cd7a2ecc6e6cd9736ff794b3ad5cb54d81d8cbc2e414d0b870 + # via -r software/thunderscope/requirements.in +netifaces==0.11.0 \ + --hash=sha256:043a79146eb2907edf439899f262b3dfe41717d34124298ed281139a8b93ca32 \ + --hash=sha256:08e3f102a59f9eaef70948340aeb6c89bd09734e0dca0f3b82720305729f63ea \ + --hash=sha256:0f6133ac02521270d9f7c490f0c8c60638ff4aec8338efeff10a1b51506abe85 \ + --hash=sha256:18917fbbdcb2d4f897153c5ddbb56b31fa6dd7c3fa9608b7e3c3a663df8206b5 \ + --hash=sha256:2479bb4bb50968089a7c045f24d120f37026d7e802ec134c4490eae994c729b5 \ + --hash=sha256:2650beee182fed66617e18474b943e72e52f10a24dc8cac1db36c41ee9c041b7 \ + --hash=sha256:28f4bf3a1361ab3ed93c5ef360c8b7d4a4ae060176a3529e72e5e4ffc4afd8b0 \ + --hash=sha256:3ecb3f37c31d5d51d2a4d935cfa81c9bc956687c6f5237021b36d6fdc2815b2c \ + --hash=sha256:469fc61034f3daf095e02f9f1bbac07927b826c76b745207287bc594884cfd05 \ + --hash=sha256:48324183af7f1bc44f5f197f3dad54a809ad1ef0c78baee2c88f16a5de02c4c9 \ + --hash=sha256:50721858c935a76b83dd0dd1ab472cad0a3ef540a1408057624604002fcfb45b \ + --hash=sha256:54ff6624eb95b8a07e79aa8817288659af174e954cca24cdb0daeeddfc03c4ff \ + --hash=sha256:5be83986100ed1fdfa78f11ccff9e4757297735ac17391b95e17e74335c2047d \ + --hash=sha256:5f9ca13babe4d845e400921973f6165a4c2f9f3379c7abfc7478160e25d196a4 \ + --hash=sha256:73ff21559675150d31deea8f1f8d7e9a9a7e4688732a94d71327082f517fc6b4 \ + --hash=sha256:7dbb71ea26d304e78ccccf6faccef71bb27ea35e259fb883cfd7fd7b4f17ecb1 \ + --hash=sha256:815eafdf8b8f2e61370afc6add6194bd5a7252ae44c667e96c4c1ecf418811e4 \ + --hash=sha256:841aa21110a20dc1621e3dd9f922c64ca64dd1eb213c47267a2c324d823f6c8f \ + --hash=sha256:84e4d2e6973eccc52778735befc01638498781ce0e39aa2044ccfd2385c03246 \ + --hash=sha256:8f7da24eab0d4184715d96208b38d373fd15c37b0dafb74756c638bd619ba150 \ + --hash=sha256:96c0fe9696398253f93482c84814f0e7290eee0bfec11563bd07d80d701280c3 \ + --hash=sha256:aab1dbfdc55086c789f0eb37affccf47b895b98d490738b81f3b2360100426be \ + --hash=sha256:c03fb2d4ef4e393f2e6ffc6376410a22a3544f164b336b3a355226653e5efd89 \ + --hash=sha256:c37a1ca83825bc6f54dddf5277e9c65dec2f1b4d0ba44b8fd42bc30c91aa6ea1 \ + --hash=sha256:c92ff9ac7c2282009fe0dcb67ee3cd17978cffbe0c8f4b471c00fe4325c9b4d4 \ + --hash=sha256:c9a3a47cd3aaeb71e93e681d9816c56406ed755b9442e981b07e3618fb71d2ac \ + --hash=sha256:cb925e1ca024d6f9b4f9b01d83215fd00fe69d095d0255ff3f64bffda74025c8 \ + --hash=sha256:d07b01c51b0b6ceb0f09fc48ec58debd99d2c8430b09e56651addeaf5de48048 \ + --hash=sha256:e76c7f351e0444721e85f975ae92718e21c1f361bda946d60a214061de1f00a1 \ + --hash=sha256:eb4813b77d5df99903af4757ce980a98c4d702bbcb81f32a0b305a1537bdf0b1 + # via -r software/thunderscope/requirements.in +numpy==1.26.4 \ + --hash=sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b \ + --hash=sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818 \ + --hash=sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20 \ + --hash=sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0 \ + --hash=sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010 \ + --hash=sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a \ + --hash=sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea \ + --hash=sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c \ + --hash=sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71 \ + --hash=sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110 \ + --hash=sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be \ + --hash=sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a \ + --hash=sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a \ + --hash=sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5 \ + --hash=sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed \ + --hash=sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd \ + --hash=sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c \ + --hash=sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e \ + --hash=sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0 \ + --hash=sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c \ + --hash=sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a \ + --hash=sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b \ + --hash=sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0 \ + --hash=sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6 \ + --hash=sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2 \ + --hash=sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a \ + --hash=sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30 \ + --hash=sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218 \ + --hash=sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5 \ + --hash=sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07 \ + --hash=sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2 \ + --hash=sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4 \ + --hash=sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764 \ + --hash=sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef \ + --hash=sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3 \ + --hash=sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f + # via + # -r software/thunderscope/requirements.in + # pyqtgraph +packaging==26.0 \ + --hash=sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4 \ + --hash=sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529 + # via qtpy +protobuf==6.31.1 \ + --hash=sha256:0414e3aa5a5f3ff423828e1e6a6e907d6c65c1d5b7e6e975793d5590bdeecc16 \ + --hash=sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447 \ + --hash=sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6 \ + --hash=sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402 \ + --hash=sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e \ + --hash=sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9 \ + --hash=sha256:8764cf4587791e7564051b35524b72844f845ad0bb011704c3736cce762d8fe9 \ + --hash=sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39 \ + --hash=sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a + # via -r software/thunderscope/requirements.in +pyqt-toast-notification==1.3.2 \ + --hash=sha256:135736ec0f16bff41104dee3c60ac318e5d55ae3378bf26892c6d08c36088ae6 \ + --hash=sha256:82688101202737736d51ab6c74a573b32266ecb7c8b0002f913407bd369737d9 + # via -r software/thunderscope/requirements.in +pyqt6-qt6==6.8.1 \ + --hash=sha256:006d786693d0511fbcf184a862edbd339c6ed1bb3bd9de363d73a19ed4b23dff \ + --hash=sha256:08065d595f1e6fc2dde9f4450eeff89082f4bad26f600a8e9b9cc5966716bfcf \ + --hash=sha256:1eb8460a1fdb38d0b2458c2974c01d471c1e59e4eb19ea63fc447aaba3ad530e \ + --hash=sha256:20843cb86bd94942d1cd99e39bf1aeabb875b241a35a8ab273e4bbbfa63776db \ + --hash=sha256:2f4b8b55b1414b93f340f22e8c88d25550efcdebc4b65a3927dd947b73bd4358 \ + --hash=sha256:98aa99fe38ae68c5318284cd28f3479ba538c40bf6ece293980abae0925c1b24 \ + --hash=sha256:9f3790c4ce4dc576e48b8718d55fb8743057e6cbd53a6ca1dd253ffbac9b7287 \ + --hash=sha256:a8bc2ed4ee5e7c6ff4dd1c7db0b27705d151fee5dc232bbd1bf17618f937f515 \ + --hash=sha256:d6ca5d2b9d2ec0ee4a814b2175f641a5c4299cb80b45e0f5f8356632663f89b3 + # via -r software/thunderscope/requirements.in +pyqtdarktheme-fork==2.3.2 \ + --hash=sha256:3ea94fed5df262d960378409357c63032639f749794d766f41a45ad8558b2523 \ + --hash=sha256:d96ee64f0884678fad9b6bc352d5e37d84ca786fa60ed32ffaa7e6c6bc67e964 + # via -r software/thunderscope/requirements.in +pyqtgraph==0.13.7 \ + --hash=sha256:64f84f1935c6996d0e09b1ee66fe478a7771e3ca6f3aaa05f00f6e068321d9e3 \ + --hash=sha256:7754edbefb6c367fa0dfb176e2d0610da3ada20aa7a5318516c74af5fb72bf7a + # via -r software/thunderscope/requirements.in +qtawesome==1.4.0 \ + --hash=sha256:783e414d1317f3e978bf67ea8e8a1b1498bad9dbd305dec814027e3b50521be6 \ + --hash=sha256:a4d689fa071c595aa6184171ce1f0f847677cb8d2db45382c43129f1d72a3d93 + # via -r software/thunderscope/requirements.in +qtpy==2.4.3 \ + --hash=sha256:72095afe13673e017946cc258b8d5da43314197b741ed2890e563cf384b51aa1 \ + --hash=sha256:db744f7832e6d3da90568ba6ccbca3ee2b3b4a890c3d6fbbc63142f6e4cdf5bb + # via + # pyqt-toast-notification + # qtawesome From 5e15f1a9cc506a6aa141e2d322cb85b7491dd6c5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 7 Feb 2026 20:53:33 +0000 Subject: [PATCH 27/34] [pre-commit.ci lite] apply automatic fixes --- src/software/simulated_tests/excessive_dribbling.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/excessive_dribbling.py index 5d9c42a20f..01c58e9516 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/excessive_dribbling.py @@ -28,7 +28,10 @@ def get_validation_status( # Use world calculation of dribbling distance, which uses implementation # of initial position of bot to final position of BALL - if world.HasField("dribble_displacement") and world.dribble_displacement is not None: + if ( + world.HasField("dribble_displacement") + and world.dribble_displacement is not None + ): dribble_disp = world.dribble_displacement dist = tbots_cpp.createSegment(dribble_disp).length() if dist > ( From c0632cd1cff25135d8b977b43b29f27469a5205b Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Sat, 7 Feb 2026 13:21:05 -0800 Subject: [PATCH 28/34] Revert "Added overrides" This reverts commit 6d9a3638a9475d73e2990f407ca6471b52ed5497. --- src/software/simulated_tests/excessive_dribbling.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/excessive_dribbling.py index 5d9c42a20f..ed8bd63a48 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/excessive_dribbling.py @@ -13,7 +13,6 @@ class ExcessivelyDribbling(Validation): """Checks if any friendly robot is excessively dribbling the ball, i.e. for over 1m.""" - @override def get_validation_status( self, world, @@ -39,7 +38,6 @@ def get_validation_status( return ValidationStatus.PASSING - @override def get_validation_geometry(self, world) -> ValidationGeometry: """(override) Shows the max allowed dribbling circle""" if world.HasField("dribble_displacement"): @@ -51,7 +49,6 @@ def get_validation_geometry(self, world) -> ValidationGeometry: ) return create_validation_geometry([]) - @override def __repr__(self): return "Check that the dribbling robot has not dribbled for more than 1m" From d36bd7807c2add08af3cd7fa8e494fdef458efab Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Sat, 7 Feb 2026 13:44:55 -0800 Subject: [PATCH 29/34] revert --- src/software/simulated_tests/excessive_dribbling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/excessive_dribbling.py index ed8bd63a48..4d4aa4de63 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/excessive_dribbling.py @@ -25,7 +25,7 @@ def get_validation_status( PASSING when the robot is not excessively dribbling """ # Use world calculation of dribbling distance, which uses implementation - # of initial position of bot to final position of BALL + # of initial position of BOT to final position of BALL if world.HasField("dribble_displacement") and world.dribble_displacement is not None: dribble_disp = world.dribble_displacement From 3edb21c097b158687c5da1c427369f379e09d274 Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Sat, 7 Feb 2026 13:47:06 -0800 Subject: [PATCH 30/34] revert --- .../simulated_tests/excessive_dribbling.py | 2 +- .../thunderscope/requirements_lock.txt | 137 ------------------ 2 files changed, 1 insertion(+), 138 deletions(-) diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/excessive_dribbling.py index 4d4aa4de63..84d259d6aa 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/excessive_dribbling.py @@ -27,7 +27,7 @@ def get_validation_status( # Use world calculation of dribbling distance, which uses implementation # of initial position of BOT to final position of BALL - if world.HasField("dribble_displacement") and world.dribble_displacement is not None: + if world.hasValue("dribble_displacement"): dribble_disp = world.dribble_displacement dist = tbots_cpp.createSegment(dribble_disp).length() if dist > ( diff --git a/src/software/thunderscope/requirements_lock.txt b/src/software/thunderscope/requirements_lock.txt index a8731b36d6..e69de29bb2 100644 --- a/src/software/thunderscope/requirements_lock.txt +++ b/src/software/thunderscope/requirements_lock.txt @@ -1,137 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# bazel run //software/thunderscope:requirements.update -# -colorama==0.4.6 \ - --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ - --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 - # via -r software/thunderscope/requirements.in -darkdetect==0.7.1 \ - --hash=sha256:3efe69f8ecd5f1b7f4fbb0d1d93f656b0e493c45cc49222380ffe2a529cbc866 \ - --hash=sha256:47be3cf5134432ddb616bbffc927237718407914993c82809983e7ccebf49013 - # via pyqtdarktheme-fork -evdev==1.7.0 ; sys_platform == "linux" \ - --hash=sha256:95bd2a1e0c6ce2cd7a2ecc6e6cd9736ff794b3ad5cb54d81d8cbc2e414d0b870 - # via -r software/thunderscope/requirements.in -netifaces==0.11.0 \ - --hash=sha256:043a79146eb2907edf439899f262b3dfe41717d34124298ed281139a8b93ca32 \ - --hash=sha256:08e3f102a59f9eaef70948340aeb6c89bd09734e0dca0f3b82720305729f63ea \ - --hash=sha256:0f6133ac02521270d9f7c490f0c8c60638ff4aec8338efeff10a1b51506abe85 \ - --hash=sha256:18917fbbdcb2d4f897153c5ddbb56b31fa6dd7c3fa9608b7e3c3a663df8206b5 \ - --hash=sha256:2479bb4bb50968089a7c045f24d120f37026d7e802ec134c4490eae994c729b5 \ - --hash=sha256:2650beee182fed66617e18474b943e72e52f10a24dc8cac1db36c41ee9c041b7 \ - --hash=sha256:28f4bf3a1361ab3ed93c5ef360c8b7d4a4ae060176a3529e72e5e4ffc4afd8b0 \ - --hash=sha256:3ecb3f37c31d5d51d2a4d935cfa81c9bc956687c6f5237021b36d6fdc2815b2c \ - --hash=sha256:469fc61034f3daf095e02f9f1bbac07927b826c76b745207287bc594884cfd05 \ - --hash=sha256:48324183af7f1bc44f5f197f3dad54a809ad1ef0c78baee2c88f16a5de02c4c9 \ - --hash=sha256:50721858c935a76b83dd0dd1ab472cad0a3ef540a1408057624604002fcfb45b \ - --hash=sha256:54ff6624eb95b8a07e79aa8817288659af174e954cca24cdb0daeeddfc03c4ff \ - --hash=sha256:5be83986100ed1fdfa78f11ccff9e4757297735ac17391b95e17e74335c2047d \ - --hash=sha256:5f9ca13babe4d845e400921973f6165a4c2f9f3379c7abfc7478160e25d196a4 \ - --hash=sha256:73ff21559675150d31deea8f1f8d7e9a9a7e4688732a94d71327082f517fc6b4 \ - --hash=sha256:7dbb71ea26d304e78ccccf6faccef71bb27ea35e259fb883cfd7fd7b4f17ecb1 \ - --hash=sha256:815eafdf8b8f2e61370afc6add6194bd5a7252ae44c667e96c4c1ecf418811e4 \ - --hash=sha256:841aa21110a20dc1621e3dd9f922c64ca64dd1eb213c47267a2c324d823f6c8f \ - --hash=sha256:84e4d2e6973eccc52778735befc01638498781ce0e39aa2044ccfd2385c03246 \ - --hash=sha256:8f7da24eab0d4184715d96208b38d373fd15c37b0dafb74756c638bd619ba150 \ - --hash=sha256:96c0fe9696398253f93482c84814f0e7290eee0bfec11563bd07d80d701280c3 \ - --hash=sha256:aab1dbfdc55086c789f0eb37affccf47b895b98d490738b81f3b2360100426be \ - --hash=sha256:c03fb2d4ef4e393f2e6ffc6376410a22a3544f164b336b3a355226653e5efd89 \ - --hash=sha256:c37a1ca83825bc6f54dddf5277e9c65dec2f1b4d0ba44b8fd42bc30c91aa6ea1 \ - --hash=sha256:c92ff9ac7c2282009fe0dcb67ee3cd17978cffbe0c8f4b471c00fe4325c9b4d4 \ - --hash=sha256:c9a3a47cd3aaeb71e93e681d9816c56406ed755b9442e981b07e3618fb71d2ac \ - --hash=sha256:cb925e1ca024d6f9b4f9b01d83215fd00fe69d095d0255ff3f64bffda74025c8 \ - --hash=sha256:d07b01c51b0b6ceb0f09fc48ec58debd99d2c8430b09e56651addeaf5de48048 \ - --hash=sha256:e76c7f351e0444721e85f975ae92718e21c1f361bda946d60a214061de1f00a1 \ - --hash=sha256:eb4813b77d5df99903af4757ce980a98c4d702bbcb81f32a0b305a1537bdf0b1 - # via -r software/thunderscope/requirements.in -numpy==1.26.4 \ - --hash=sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b \ - --hash=sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818 \ - --hash=sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20 \ - --hash=sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0 \ - --hash=sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010 \ - --hash=sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a \ - --hash=sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea \ - --hash=sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c \ - --hash=sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71 \ - --hash=sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110 \ - --hash=sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be \ - --hash=sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a \ - --hash=sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a \ - --hash=sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5 \ - --hash=sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed \ - --hash=sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd \ - --hash=sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c \ - --hash=sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e \ - --hash=sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0 \ - --hash=sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c \ - --hash=sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a \ - --hash=sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b \ - --hash=sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0 \ - --hash=sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6 \ - --hash=sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2 \ - --hash=sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a \ - --hash=sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30 \ - --hash=sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218 \ - --hash=sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5 \ - --hash=sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07 \ - --hash=sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2 \ - --hash=sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4 \ - --hash=sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764 \ - --hash=sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef \ - --hash=sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3 \ - --hash=sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f - # via - # -r software/thunderscope/requirements.in - # pyqtgraph -packaging==26.0 \ - --hash=sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4 \ - --hash=sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529 - # via qtpy -protobuf==6.31.1 \ - --hash=sha256:0414e3aa5a5f3ff423828e1e6a6e907d6c65c1d5b7e6e975793d5590bdeecc16 \ - --hash=sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447 \ - --hash=sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6 \ - --hash=sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402 \ - --hash=sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e \ - --hash=sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9 \ - --hash=sha256:8764cf4587791e7564051b35524b72844f845ad0bb011704c3736cce762d8fe9 \ - --hash=sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39 \ - --hash=sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a - # via -r software/thunderscope/requirements.in -pyqt-toast-notification==1.3.2 \ - --hash=sha256:135736ec0f16bff41104dee3c60ac318e5d55ae3378bf26892c6d08c36088ae6 \ - --hash=sha256:82688101202737736d51ab6c74a573b32266ecb7c8b0002f913407bd369737d9 - # via -r software/thunderscope/requirements.in -pyqt6-qt6==6.8.1 \ - --hash=sha256:006d786693d0511fbcf184a862edbd339c6ed1bb3bd9de363d73a19ed4b23dff \ - --hash=sha256:08065d595f1e6fc2dde9f4450eeff89082f4bad26f600a8e9b9cc5966716bfcf \ - --hash=sha256:1eb8460a1fdb38d0b2458c2974c01d471c1e59e4eb19ea63fc447aaba3ad530e \ - --hash=sha256:20843cb86bd94942d1cd99e39bf1aeabb875b241a35a8ab273e4bbbfa63776db \ - --hash=sha256:2f4b8b55b1414b93f340f22e8c88d25550efcdebc4b65a3927dd947b73bd4358 \ - --hash=sha256:98aa99fe38ae68c5318284cd28f3479ba538c40bf6ece293980abae0925c1b24 \ - --hash=sha256:9f3790c4ce4dc576e48b8718d55fb8743057e6cbd53a6ca1dd253ffbac9b7287 \ - --hash=sha256:a8bc2ed4ee5e7c6ff4dd1c7db0b27705d151fee5dc232bbd1bf17618f937f515 \ - --hash=sha256:d6ca5d2b9d2ec0ee4a814b2175f641a5c4299cb80b45e0f5f8356632663f89b3 - # via -r software/thunderscope/requirements.in -pyqtdarktheme-fork==2.3.2 \ - --hash=sha256:3ea94fed5df262d960378409357c63032639f749794d766f41a45ad8558b2523 \ - --hash=sha256:d96ee64f0884678fad9b6bc352d5e37d84ca786fa60ed32ffaa7e6c6bc67e964 - # via -r software/thunderscope/requirements.in -pyqtgraph==0.13.7 \ - --hash=sha256:64f84f1935c6996d0e09b1ee66fe478a7771e3ca6f3aaa05f00f6e068321d9e3 \ - --hash=sha256:7754edbefb6c367fa0dfb176e2d0610da3ada20aa7a5318516c74af5fb72bf7a - # via -r software/thunderscope/requirements.in -qtawesome==1.4.0 \ - --hash=sha256:783e414d1317f3e978bf67ea8e8a1b1498bad9dbd305dec814027e3b50521be6 \ - --hash=sha256:a4d689fa071c595aa6184171ce1f0f847677cb8d2db45382c43129f1d72a3d93 - # via -r software/thunderscope/requirements.in -qtpy==2.4.3 \ - --hash=sha256:72095afe13673e017946cc258b8d5da43314197b741ed2890e563cf384b51aa1 \ - --hash=sha256:db744f7832e6d3da90568ba6ccbca3ee2b3b4a890c3d6fbbc63142f6e4cdf5bb - # via - # pyqt-toast-notification - # qtawesome From 6cef8481d02b8c9e961580f8ec96176e6966dbf4 Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Sat, 7 Feb 2026 13:49:36 -0800 Subject: [PATCH 31/34] revert merge --- src/software/simulated_tests/excessive_dribbling.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/software/simulated_tests/excessive_dribbling.py b/src/software/simulated_tests/excessive_dribbling.py index 84d259d6aa..5d9c42a20f 100644 --- a/src/software/simulated_tests/excessive_dribbling.py +++ b/src/software/simulated_tests/excessive_dribbling.py @@ -13,6 +13,7 @@ class ExcessivelyDribbling(Validation): """Checks if any friendly robot is excessively dribbling the ball, i.e. for over 1m.""" + @override def get_validation_status( self, world, @@ -25,9 +26,9 @@ def get_validation_status( PASSING when the robot is not excessively dribbling """ # Use world calculation of dribbling distance, which uses implementation - # of initial position of BOT to final position of BALL + # of initial position of bot to final position of BALL - if world.hasValue("dribble_displacement"): + if world.HasField("dribble_displacement") and world.dribble_displacement is not None: dribble_disp = world.dribble_displacement dist = tbots_cpp.createSegment(dribble_disp).length() if dist > ( @@ -38,6 +39,7 @@ def get_validation_status( return ValidationStatus.PASSING + @override def get_validation_geometry(self, world) -> ValidationGeometry: """(override) Shows the max allowed dribbling circle""" if world.HasField("dribble_displacement"): @@ -49,6 +51,7 @@ def get_validation_geometry(self, world) -> ValidationGeometry: ) return create_validation_geometry([]) + @override def __repr__(self): return "Check that the dribbling robot has not dribbled for more than 1m" From 41344a30f8ca6b829c671788d953166e52e8cac5 Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Sat, 7 Feb 2026 13:51:36 -0800 Subject: [PATCH 32/34] revert --- .github/actions/environment-setup/action.yml | 17 +- .github/release-drafter.yml | 11 -- .github/workflows/main.yml | 10 +- .github/workflows/release.yml | 148 ------------------ .gitignore | 4 - docs/robot-software-architecture.md | 4 +- docs/setup-pi.md | 121 -------------- docs/useful-robot-commands.md | 6 +- environment_setup/macos_requirements.txt | 13 -- environment_setup/setup_software.sh | 5 - environment_setup/setup_software_mac.sh | 90 ----------- environment_setup/util.sh | 84 +++------- src/.bazelrc | 2 +- src/MODULE.bazel | 3 +- src/MODULE.bazel.lock | 8 +- .../src/amun/simulator/simulator.cpp | 5 +- src/software/BUILD | 9 -- src/software/ai/hl/stp/play/play.cpp | 3 +- .../stp/tactic/goalie/goalie_tactic_test.py | 8 +- .../pass_defender/pass_defender_fsm.cpp | 7 - src/software/embedded/BUILD | 5 - src/software/embedded/ansible/BUILD | 1 - .../embedded/ansible/tasks/setup_systemd.yml | 11 -- .../embedded/hash_thunderloop_binary.sh | 2 +- .../field_tests/field_test_fixture.py | 1 - src/software/logger/BUILD | 14 +- src/software/logger/compat_flags.h | 17 -- src/software/logger/csv_sink.cpp | 6 +- src/software/logger/log_merger.cpp | 6 +- src/software/logger/log_merger.h | 16 +- src/software/logger/logger.h | 7 +- src/software/logger/proto_logger.cpp | 4 +- .../sensor_fusion/filter/ball_filter.cpp | 15 +- .../sensor_fusion/filter/ball_filter.h | 10 +- .../simulated_tests/simulated_test_fixture.py | 2 - src/software/thunderscope/BUILD | 2 - .../binary_context_managers/BUILD | 10 -- .../binary_context_managers/full_system.py | 29 ++-- .../game_controller.py | 9 +- .../runtime_installer.py | 67 -------- .../binary_context_managers/runtime_loader.py | 139 ---------------- .../runtime_manager.py | 49 ------ src/software/thunderscope/constants.py | 14 -- src/software/thunderscope/gl/widgets/BUILD | 20 --- .../gl/widgets/gl_gamecontroller_toolbar.py | 35 ----- .../gl/widgets/gl_runtime_installer.py | 56 ------- .../gl/widgets/gl_runtime_selector.py | 100 ------------ src/software/thunderscope/requirements.in | 2 +- .../thunderscope/requirements_lock.darwin.txt | 126 --------------- .../thunderscope/requirements_lock.txt | 135 ++++++++++++++++ .../thunderscope/robot_diagnostics/BUILD | 8 +- .../robot_diagnostics/handheld_controller.py | 7 +- .../handheld_controller_widget.py | 8 +- .../thunderscope/thunderscope_main.py | 19 +-- src/software/thunderscope/util.py | 8 - 55 files changed, 240 insertions(+), 1278 deletions(-) delete mode 100644 .github/release-drafter.yml delete mode 100644 .github/workflows/release.yml delete mode 100644 docs/setup-pi.md delete mode 100644 environment_setup/macos_requirements.txt delete mode 100755 environment_setup/setup_software_mac.sh delete mode 100644 src/software/logger/compat_flags.h delete mode 100644 src/software/thunderscope/binary_context_managers/runtime_installer.py delete mode 100644 src/software/thunderscope/binary_context_managers/runtime_loader.py delete mode 100644 src/software/thunderscope/binary_context_managers/runtime_manager.py delete mode 100644 src/software/thunderscope/gl/widgets/gl_runtime_installer.py delete mode 100644 src/software/thunderscope/gl/widgets/gl_runtime_selector.py delete mode 100644 src/software/thunderscope/requirements_lock.darwin.txt diff --git a/.github/actions/environment-setup/action.yml b/.github/actions/environment-setup/action.yml index ff9cdc160b..4cf85911fd 100644 --- a/.github/actions/environment-setup/action.yml +++ b/.github/actions/environment-setup/action.yml @@ -1,12 +1,4 @@ name: Environment Setup - -on: - workflow_call: - inputs: - os: - required: true - type: string - runs: using: "composite" steps: @@ -18,15 +10,8 @@ runs: sudo rm -rf /usr/share/dotnet sudo rm -rf /usr/local/lib/android sudo rm -rf /opt/ghc - + - name: Run setup_software.sh shell: bash - if: runner.os == 'Linux' run: | "${GITHUB_WORKSPACE}"/environment_setup/setup_software.sh - - - name: Run setup_software_mac.sh - shell: bash - if: runner.os == 'macOS' - run: | - "${GITHUB_WORKSPACE}"/environment_setup/setup_software_mac.sh diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml deleted file mode 100644 index ff313816bd..0000000000 --- a/.github/release-drafter.yml +++ /dev/null @@ -1,11 +0,0 @@ -change-template: '- $TITLE @$AUTHOR (#$NUMBER)' -name-template: 'v$RESOLVED_VERSION' -tag-template: 'v$RESOLVED_VERSION' -change-title-escapes: '\<*_&' -exclude-labels: - - 'skip-changelog' - -template: | - ## Changes - - $CHANGES diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5b1c94d459..ebe85c735e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,8 +31,7 @@ jobs: -//software/ai/hl/... \ -//software/field_tests/... \ -//software/embedded/... \ - -//toolchains/cc/... \ - -//software:unix_full_system_tar_gen + -//toolchains/cc/... - name: Jetson Nano Build Test run: | @@ -61,8 +60,7 @@ jobs: -//software/ai/hl/... \ -//software/field_tests/... \ -//software/ai/navigator/... \ - -//toolchains/cc/... \ - -//software:unix_full_system_tar_gen + -//toolchains/cc/... robot-tests: name: Robot Software Tests @@ -110,8 +108,8 @@ jobs: //software:unix_full_system \ //software/simulated_tests/... \ //software/ai/hl/... \ - //software/ai/navigator/... - + //software/ai/navigator/... + - name: Upload simulated test proto logs # Ensure that simulated test logs get uploaded if: always() diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 64b7dd6151..0000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,148 +0,0 @@ -name: Release binary - -on: - schedule: - - cron: '0 0 * * 0' # Weekly Patch (Sundays) - - cron: '0 0 1 * *' # Monthly Minor (1st of the month) - workflow_dispatch: - inputs: - version_type: - description: 'Manual Release Level' - required: true - default: 'major' - type: choice - options: - - major - - minor - - patch - release_tag: - description: 'Specify the new release tag name (e.g., v1.2.3)' - required: false - type: string - - target_commit: - description: 'Optional commit SHA/branch to release (leave empty for current HEAD)' - required: false - type: string - default: '' - -jobs: - prepare_release: - runs-on: ubuntu-latest - outputs: - tag: ${{ steps.bump_logic.outputs.tag }} - should_release: ${{ steps.check.outputs.count > 0 }} - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.event.inputs.target_commit || github.sha }} - - - name: Check for recent commits - id: check - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "count=1" >> $GITHUB_OUTPUT - else - COUNT=$(git log --since="1 week ago" --oneline | wc -l) - echo "count=$COUNT" >> $GITHUB_OUTPUT - fi - - - name: Determine Version Bump - if: steps.check.outputs.count > 0 - id: bump_logic - run: | - if [ "${{ github.event.inputs.release_tag }}" = "" ]; then - BUMP="patch" - if [ "$(date +%d)" = "01" ]; then BUMP="minor"; fi - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - BUMP="${{ github.event.inputs.version_type }}" - fi - - LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v1.0.0") - # Strip the 'v' prefix - BASE_VERSION=${LATEST_TAG#v} - - IFS='.' read -r major minor patch <<< "$BASE_VERSION" - - if [ "$BUMP" = "major" ]; then - major=$((major + 1)); minor=0; patch=0 - elif [ "$BUMP" = "minor" ]; then - minor=$((minor + 1)); patch=0 - else - patch=$((patch + 1)) - fi - - NEW_TAG="v$major.$minor.$patch" - else - NEW_TAG="${{ github.event.inputs.release_tag }}" - fi - echo "tag=$NEW_TAG" >> $GITHUB_OUTPUT - echo "Using version: $NEW_TAG" - - - name: Draft Release Notes - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: release-drafter/release-drafter@v6 - with: - version: ${{ steps.bump_logic.outputs.tag }} - tag: ${{ steps.bump_logic.outputs.tag }} - - - name: Create GitHub Release - if: steps.check.outputs.count > 0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh release create "${{ steps.bump_logic.outputs.tag }}" \ - --title "${{ steps.bump_logic.outputs.tag }}" \ - --generate-notes \ - --draft - - upload_assets: - needs: prepare_release - if: needs.prepare_release.outputs.should_release == 'true' - strategy: - matrix: - platform: [ubuntu-x86, mac-arm64] - include: - - platform: ubuntu-x86 - runner: ubuntu-24.04 - - platform: mac-arm64 - runner: macos-latest - runs-on: ${{ matrix.runner }} - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.event.inputs.target_commit || github.ref }} - - - uses: ./.github/actions/environment-setup - - - name: Build Binaries - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - cd src - TAG="${{ needs.prepare_release.outputs.tag }}" - bazel build --show_timestamps --copt=-O3 --verbose_failures \ - -- //software:unix_full_system_tar_gen - mv bazel-bin/software/unix_full_system_tar_gen.tar.gz "${{ runner.temp }}/unix_full_system_${{ needs.prepare_release.outputs.tag }}_${{ matrix.platform }}.tar.gz" - gh release upload "$TAG" "${{ runner.temp }}/unix_full_system_${{ needs.prepare_release.outputs.tag }}_${{ matrix.platform }}.tar.gz" - - publish_release: - needs: [prepare_release, upload_assets] - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.inputs.target_commit || github.sha }} - - name: Undraft Release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: gh release edit "${{ needs.prepare_release.outputs.tag }}" --draft=false diff --git a/.gitignore b/.gitignore index 076a0b3477..989fc31f59 100644 --- a/.gitignore +++ b/.gitignore @@ -158,7 +158,3 @@ src/.clangd # external is a special directory for bazel so it should be ignored src/external - -# macOS folder info -.DS_Store - diff --git a/docs/robot-software-architecture.md b/docs/robot-software-architecture.md index bacc1d1825..08f9ecd6bd 100644 --- a/docs/robot-software-architecture.md +++ b/docs/robot-software-architecture.md @@ -35,7 +35,9 @@ More commands available [here](useful-robot-commands.md#off-robot-commands) ## Systemd -[Systemd](https://www.freedesktop.org/wiki/Software/systemd/) allows us to have services which start as soon as we boot the robot, will automatically restart and are individually controllable. All services have the file {service}.service, which controls the configuration of that service. Our core service brought up by systemd is thunderloop. The thunderloop.service file can be seen [here](https://github.com/UBC-Thunderbots/Software/blob/master/src/software/embedded/linux_configs/systemd/thunderloop.service). +[Systemd](https://www.freedesktop.org/wiki/Software/systemd/) allows us to have services which start as soon as we boot the robot, will automatically restart and are individually controllable. All services have the file {service}.service, which controls the configuration of that service. Currently we have a service for thunderloop, announcements and display + +To learn more about how it works, [see the RFC](https://docs.google.com/document/d/1hN3Us2Vjr8z6ihqUVp_3L7rrjKc-EZ-l2hZJc31gNOc/edit) ## Redis diff --git a/docs/setup-pi.md b/docs/setup-pi.md deleted file mode 100644 index d4549e41fc..0000000000 --- a/docs/setup-pi.md +++ /dev/null @@ -1,121 +0,0 @@ -# Raspberry Pi Setup Guide - -## Install Raspberry Pi OS onto MicroSD card - -### Introduction - -The microSD card in a Raspberry Pi holds its Operating System and acts as its primary storage area. The Pi will not work without it. - -The Raspberry Pi 5 is compatible with many different (primarily Linux-based) operating systems. The one we use is the standard Raspberry Pi OS (also Linux-based). - -### Install OS Using Raspberry Pi Imager - -To install the Raspberry Pi OS onto a microSD card, we use the Raspberry Pi Imager program. Install it [here](https://www.raspberrypi.com/software/). - -*Note: if using the "Download for Linux" option, you will have to right click on the installed `.AppImage` file and select `Properties` → `Permissions` → `Allow Executing File as Program` (this may appear as a checkbox) - -When you open the imager, you should see something like this: - -* image - -Once installed, follow these steps to set up the Raspberry Pi: -1. Plug the microSD card into your device. - * If your device has an SD card slot, use a microSD to SD card adapter. This adapter is available in the tbots EDC space as of Jan 2026. - * If your device only has a USB slot, use a combination of a microSD to SD card adapter and USB SD card reader. Again, both of these adapters are available in the tbots EDC space. -2. Open and configure the Raspberry Pi Imager program as follows: - * Raspberry Pi Device: Raspberry Pi 5 - * Operating System: Raspberry Pi OS (64-bit) - * Storage: `` - * Customization: - * Hostname: ``.local (Ex: `aimbot`.local). The `local` part is autofilled by the Raspberry Pi Imager. - * Localization → Timezone: America/Vancouver - * User → Username: robot - * User → Password: `` (ask someone if you don't know what the correct password is) - * WiFi → Secure Network → SSID: `` (`tbots` as of Jan 2026) - * WiFi → Password: `` (ask someone if you don't know what the correct password is) - * Remote Access: Enable SSH and select "Use Password Authentication" - * Click the "Write" button and wait until complete - - Once the write process is complete, the Raspberry Pi OS is set up! You can verify the write was successful using several methods; the following is only one of these methods: - * Connect the Raspberry Pi to an HDMI output screen using a microHDMI to HDMI cable. - * You can also use an HDMI to HDMI cable with a microHDMI to HDMI adapter. We have one of these adapters in the tbots EDC space as of Jan 2026. - * The output screen on the HDMI should appear similar to a default laptop/PC screen. If this is the case, the write was successful. - * If you do not see the above, and instead see text on a black screen with messages akin to "Unable to read partition as FAT" - the write was not successful. - -## Configure Raspberry Pi with Thunderbots Service - -### Introduction - -We use the Raspberry Pi OS, which is based on the Linux kernel. The Linux kernel uses systemd as its service manager. Our core service is [thunderloop.service](https://github.com/UBC-Thunderbots/Software/blob/master/src/software/embedded/linux_configs/systemd/thunderloop.service). - -To run and manage the thunderloop service on the Pi, however, there are many dependencies (drivers, admin control, internet, etc.) that must be set up first. Luckily, all of this setup can be done using one command with an Ansible playbook. - -Read more about Ansible in our robot software architecture documentation [here](https://github.com/UBC-Thunderbots/Software/blob/master/docs/robot-software-architecture.md#ansible). - -### Configuration - -#### Internet Configuration - -Before running the Ansible command, we must do the following to configure internet access to the Pi: -1. Ensure your Raspberry Pi is connected to internet through an ethernet cable. Do this by connecting one end to the Rapsberry Pi and the other to your device (PC/laptop). Ensure your ethernet cable is not broken, as there are many non-functioning ones in the Thunderbots EDC space. -2. On your device (PC/laptop), configure network settings through the following steps: - 1. Go to network settings. You should see something like this: - - * image - - 2. Click the settings icon on the right (seen in the image above). - 3. Under the IPv4 tab, select "Shared to other computers" - - * image - - 4. Under the IPv6 tab, select "Disable" - * image - - 5. Apply changes -3. Enable IP forwarding from your device to the Pi with the following command (this has to be reconfigured every time you boot your device): -```bash -sudo sysctl -w net.ipv4.ip_forward=1 -``` -4. Enable Network Address Translation (NAT) between your Pi and device with the following commands (this has to be reconfigured every time you boot your device): -```bash -sudo iptables -t nat -A POSTROUTING -o wlo1 -j MASQUERADE -sudo iptables -A FORWARD -i eno2 -o wlo1 -j ACCEPT -sudo iptables -A FORWARD -i wlo1 -o eno2 -m state --state RELATED,ESTABLISHED -j ACCEPT -``` - -#### Thunderloop Service Configuration Through Ansible -1. SSH into the Raspberry Pi from your device by connecting to the `tbots` WiFi and typing the following in the command terminal: - * ssh `robot@` (Ex: `ssh robot@aimbot.local`) - * enter the password when prompted -2. Verify that the Pi has internet connection by pinging Google's Public DNS Service with the following command: -```bash -ping -c 3 8.8.8.8 -``` - * If successful, you should see all packets transmitted and received at some point in the return message. Ex: ```3 packets transmitted, 3 received, 0% packet loss, time 2004ms```. - * If not successful, you will see packets not received. Ex: ```3 packets transmitted, 0 received, 100% packet loss, time 2052ms```. -3. Exit the SSH connection to the Raspberry Pi with the `exit` command. -4. Change directory to `Software/src` and run the bazel ansible command: -```bash -bazel run //software/embedded/ansible:run_ansible --platforms=//toolchains/cc:robot --//software/embedded:host_platform=PI -- --playbook setup_pi.yml --hosts .local --ssh_pass -``` - * Ensure you configure `` and `` in the command above. Copy/pasting the above won't work. - * This may take a while. -5. Done! - -### Common Errors and Debugging - -**Cannot SSH into Pi** -A: Confirm that your device is connected to the `tbots` WiFi - -**Raspberry Pi shuts off (light turns red, HDMI output disconnects if connected) while the Bazel Ansible command is running** -A: This is a power brownout (voltage drops below required threshold). There are too many peripherals connected. Disconnect some and try again. - -**Raspberry Pi unresponsive and LED always solid green** -A: This is usually an indicator that the Pi's SD Card is corrupted or empty. Operational Raspberry Pi's usually have a flickering LED. Fix by reprovisioning the SD Card with the Raspberry Pi Imager (directions above). - - - - - - - diff --git a/docs/useful-robot-commands.md b/docs/useful-robot-commands.md index 1ede1bcd24..cf9415ab38 100644 --- a/docs/useful-robot-commands.md +++ b/docs/useful-robot-commands.md @@ -156,7 +156,7 @@ bazel run //software/embedded/ansible:run_ansible --platforms=//toolchains/cc:ro ### Raspberry Pi ```bash -bazel run //software/embedded/ansible:run_ansible --platforms=//toolchains/cc:robot --//software/embedded:host_platform=PI -- --playbook setup_pi.yml --hosts --ssh_pass +bazel run //software/embedded/ansible:run_ansible --platforms=//toolchains/cc:robot --//software/embedded:host_platform=PI -- --playbook setup_raspberry_pi.yml --hosts --ssh_pass ``` ## Robot Diagnostics @@ -189,7 +189,7 @@ Runs the robot auto test fixture on a robot through Ansible, which tests the mot From Software/src: ```bash -bazel run //software/embedded/ansible:run_ansible --//software/embedded:host_platform= --platforms=//toolchains/cc:robot -- --playbook robot_auto_test_playbook.yml --hosts --ssh_pass +bazel run //software/embedded/ansible:run_ansible --//software/embedded:host_platform= --platforms=//toolchains/cc:robot -- --playbook robot_auto_test_playbook.yml --hosts --ssh_pass ``` * replace the \ with the target platform for the robot (either `PI` or `NANO`) @@ -202,7 +202,7 @@ bazel run //software/embedded/ansible:run_ansible --//software/embedded:host_pla ## Systemd Services -Status shows whether the service is running and some recent logs. More logs can be found using `journalctl` shown below. More control can be achieved with `systemctl`. Currently, the only valid `` is `thunderloop`. +Status shows whether the service is running and some recent logs. More logs can be found using `journalctl` shown below. More control can be achieved with `systemctl`. Valid `` are `thunderloop`, `display`, and `wifi_announcements` ```bash service status diff --git a/environment_setup/macos_requirements.txt b/environment_setup/macos_requirements.txt deleted file mode 100644 index 8e4cf0bb42..0000000000 --- a/environment_setup/macos_requirements.txt +++ /dev/null @@ -1,13 +0,0 @@ -ansible-lint==24.12.2 -pyqtgraph==0.13.7 -thefuzz==0.19.0 -iterfzf==0.5.0.20.0 -python-Levenshtein==0.25.1 -psutil==5.9.0 -PyOpenGL==3.1.6 -ruff==0.5.5 -pyqt-toast-notification==1.3.2 -grpcio-tools==1.71.0 -platformio==6.1.18 -pyqt6==6.9.1 - diff --git a/environment_setup/setup_software.sh b/environment_setup/setup_software.sh index 2a8c9a5660..f3701b7087 100755 --- a/environment_setup/setup_software.sh +++ b/environment_setup/setup_software.sh @@ -143,10 +143,6 @@ sudo cp "$CURR_DIR/../src/software/autoref/DIV_B.txt" "/opt/tbotspython/autoRefe print_status_msg "Finished setting up AutoRef" -# setup external_runtimes -sudo mkdir /opt/tbotspython/external_runtimes -sudo chown -R $USER:$USER /opt/tbotspython/external_runtimes/ - # Install Bazel print_status_msg "Installing Bazel" @@ -164,7 +160,6 @@ print_status_msg "Done setting up cross compiler for robot software" print_status_msg "Setting Up Python Development Headers" install_python_dev_cross_compile_headers $g_arch -install_python_toolchain_headers print_status_msg "Done Setting Up Python Development Headers" print_status_msg "Setting Up PlatformIO" diff --git a/environment_setup/setup_software_mac.sh b/environment_setup/setup_software_mac.sh deleted file mode 100755 index b714235023..0000000000 --- a/environment_setup/setup_software_mac.sh +++ /dev/null @@ -1,90 +0,0 @@ -#!/bin/bash -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~# -# UBC Thunderbots macOS Software Setup -# -# This script will install all required libraries and dependencies to build -# and run the Thunderbots codebase on macOS, including the AI and unit tests. -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~# - -# Save the parent dir of this script -CURR_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) -cd "$CURR_DIR" || exit - -source util.sh - -# Since we only support MacOS Arm chips (M series), we can use "Darwin" as the identifier -# for mac setup procedures and ignore the architecture for now. -sys=$(uname -s) - -print_status_msg "Installing Utilities and Dependencies" - -# Update Homebrew -brew update - -# Install required packages -host_software_packages=( - cmake@4 - python@3.12 - bazelisk - openjdk@21 - pyqt@6 - qt@6 - node@20 - go@1.24 - clang-format@20 -) - -for pkg in "${host_software_packages[@]}"; do - if ! brew list "$pkg" &>/dev/null; then - print_status_msg "Installing $pkg..." - brew install "$pkg" - else - print_status_msg "$pkg already installed, skipping..." - fi -done - -# Set up cache -mkdir /tmp/tbots_download_cache - -# Set up Python -print_status_msg "Setting Up Python Environment" - -# Create virtual environment -sudo python3.12 -m venv /opt/tbotspython -chmod -source /opt/tbotspython/bin/activate - -# Install Python dependencies -sudo pip install --upgrade pip -sudo pip install -r macos_requirements.txt - -print_status_msg "Done Setting Up Python Environment" - -print_status_msg "Fetching game controller" -install_gamecontroller $sys - -print_status_msg "Setting up TIGERS AutoRef" -install_autoref $sys -sudo chmod +x "$CURR_DIR/../src/software/autoref/run_autoref.sh" -sudo cp "$CURR_DIR/../src/software/autoref/DIV_B.txt" "/opt/tbotspython/autoReferee/config/geometry/DIV_B.txt" -print_status_msg "Finished setting up AutoRef" - -print_status_msg "Setting up cross compiler for robot software" -install_cross_compiler $sys -print_status_msg "Done setting up cross compiler for robot software" - -print_status_msg "Setting Up Python Development Headers" -install_python_toolchain_headers -print_status_msg "Done Setting Up Python Development Headers" - -print_status_msg "Granting Permissions to /opt/tbotspython" -sudo chown -R $(id -u):$(id -g) /opt/tbotspython -print_status_msg "Done Granting Permissions to /opt/tbotspython" - -print_status_msg "Set up ansible-lint" -/opt/tbotspython/bin/ansible-galaxy collection install ansible.posix -print_status_msg "Finished setting up ansible-lint" - -print_status_msg "Software Setup Complete" -print_status_msg "Note: Some changes require a new terminal session to take effect" - diff --git a/environment_setup/util.sh b/environment_setup/util.sh index fb85ba8a2b..e3603b5510 100755 --- a/environment_setup/util.sh +++ b/environment_setup/util.sh @@ -1,20 +1,11 @@ install_autoref() { - if is_darwin $1; then - autoref_version=1.5.5 - curl -L https://github.com/TIGERs-Mannheim/AutoReferee/releases/download/${autoref_version}/autoReferee.zip -o /tmp/tbots_download_cache/autoReferee.zip - unzip -q -o -d /tmp/tbots_download_cache/ /tmp/tbots_download_cache/autoReferee.zip + autoref_commit=b30660b78728c3ce159de8ae096181a1ec52e9ba + wget -N https://github.com/TIGERs-Mannheim/AutoReferee/archive/${autoref_commit}.zip -O /tmp/tbots_download_cache/autoReferee.zip + unzip -q -o -d /tmp/tbots_download_cache/ /tmp/tbots_download_cache/autoReferee.zip - sudo mv /tmp/tbots_download_cache/autoReferee /opt/tbotspython/ - rm -rf /tmp/tbots_download_cache/autoReferee.zip - else - autoref_commit=b30660b78728c3ce159de8ae096181a1ec52e9ba - wget -N https://github.com/TIGERs-Mannheim/AutoReferee/archive/${autoref_commit}.zip -O /tmp/tbots_download_cache/autoReferee.zip - unzip -q -o -d /tmp/tbots_download_cache/ /tmp/tbots_download_cache/autoReferee.zip - - /tmp/tbots_download_cache/AutoReferee-${autoref_commit}/./gradlew installDist -p /tmp/tbots_download_cache/AutoReferee-${autoref_commit} -Dorg.gradle.java.home=/opt/tbotspython/bin/jdk - mv /tmp/tbots_download_cache/AutoReferee-${autoref_commit}/build/install/autoReferee /opt/tbotspython/ - rm -rf /tmp/tbots_download_cache/autoReferee.zip /tmp/tbots_download_cache/AutoReferee-${autoref_commit} - fi + /tmp/tbots_download_cache/AutoReferee-${autoref_commit}/./gradlew installDist -p /tmp/tbots_download_cache/AutoReferee-${autoref_commit} -Dorg.gradle.java.home=/opt/tbotspython/bin/jdk + mv /tmp/tbots_download_cache/AutoReferee-${autoref_commit}/build/install/autoReferee /opt/tbotspython/ + rm -rf /tmp/tbots_download_cache/autoReferee.zip /tmp/tbots_download_cache/AutoReferee-${autoref_commit} } install_bazel() { @@ -34,47 +25,25 @@ install_clang_format() { install_cross_compiler() { file_name=aarch64-tbots-linux-gnu-for-aarch64 - if is_darwin $1; then - full_file_name=$file_name.tar.xz - curl -L "https://raw.githubusercontent.com/UBC-Thunderbots/Software-External-Dependencies/refs/heads/main/toolchain/$full_file_name" \ - -o /tmp/tbots_download_cache/$full_file_name - tar -xf /tmp/tbots_download_cache/$full_file_name -C /tmp/tbots_download_cache/ - sudo mv /tmp/tbots_download_cache/aarch64-tbots-linux-gnu /opt/tbotspython - rm /tmp/tbots_download_cache/$full_file_name - else - if is_x86 $1; then - file_name=aarch64-tbots-linux-gnu-for-x86 - fi - full_file_name=$file_name.tar.xz - wget https://raw.githubusercontent.com/UBC-Thunderbots/Software-External-Dependencies/refs/heads/main/toolchain/$full_file_name -O /tmp/tbots_download_cache/$full_file_name - tar -xf /tmp/tbots_download_cache/$full_file_name -C /tmp/tbots_download_cache/ - sudo mv /tmp/tbots_download_cache/aarch64-tbots-linux-gnu /opt/tbotspython - rm /tmp/tbots_download_cache/$full_file_name + if is_x86 $1; then + file_name=aarch64-tbots-linux-gnu-for-x86 fi + full_file_name=$file_name.tar.xz + wget https://raw.githubusercontent.com/UBC-Thunderbots/Software-External-Dependencies/refs/heads/main/toolchain/$full_file_name -O /tmp/tbots_download_cache/$full_file_name + tar -xf /tmp/tbots_download_cache/$full_file_name -C /tmp/tbots_download_cache/ + sudo mv /tmp/tbots_download_cache/aarch64-tbots-linux-gnu /opt/tbotspython + rm /tmp/tbots_download_cache/$full_file_name } install_gamecontroller () { - if is_darwin $1; then - curl -L https://github.com/RoboCup-SSL/ssl-game-controller/archive/refs/tags/v3.17.0.zip -o /tmp/tbots_download_cache/ssl-game-controller.zip - unzip -q -o -d /tmp/tbots_download_cache/ /tmp/tbots_download_cache/ssl-game-controller.zip - cd /tmp/tbots_download_cache/ssl-game-controller-3.17.0 - make install - go build cmd/ssl-game-controller/main.go - sudo mv main /opt/tbotspython/gamecontroller - sudo chmod +x /opt/tbotspython/gamecontroller - - cd - - sudo rm -rf /tmp/tbots_download_cache/ssl-game-controller-3.17.0 /tmp/tbots_download_cache/go /tmp/tbots_download_cache/go.tar.gz /tmp/tbots_download_cache/ssl-game-controller.zip - else - arch=arm64 - if is_x86 $1; then - arch=amd64 - fi - - wget https://github.com/RoboCup-SSL/ssl-game-controller/releases/download/v3.16.1/ssl-game-controller_v3.16.1_linux_${arch} -O /tmp/tbots_download_cache/gamecontroller - sudo mv /tmp/tbots_download_cache/gamecontroller /opt/tbotspython/gamecontroller - sudo chmod +x /opt/tbotspython/gamecontroller + arch=arm64 + if is_x86 $1; then + arch=amd64 fi + + wget https://github.com/RoboCup-SSL/ssl-game-controller/releases/download/v3.16.1/ssl-game-controller_v3.16.1_linux_${arch} -O /tmp/tbots_download_cache/gamecontroller + sudo mv /tmp/tbots_download_cache/gamecontroller /opt/tbotspython/gamecontroller + sudo chmod +x /opt/tbotspython/gamecontroller } install_java () { @@ -121,11 +90,6 @@ install_python_dev_cross_compile_headers() { rm -rf /tmp/tbots_download_cache/python-3.12.0.tar.xz } -install_python_toolchain_headers() { - sudo mkdir -p /opt/tbotspython/py_headers/include/ - sudo ln -sfn "$(python3.12-config --includes | awk '{for(i=1;i<=NF;++i) if ($i ~ /^-I/) print substr($i, 3)}' | head -n1)" /opt/tbotspython/py_headers/include/ -} - is_x86() { if [[ $1 == "x86_64" ]]; then return 0 @@ -134,14 +98,6 @@ is_x86() { fi } -is_darwin() { - if [[ $1 == "Darwin" ]]; then - return 0 - else - return 1 - fi -} - print_status_msg () { echo "================================================================" echo $1 diff --git a/src/.bazelrc b/src/.bazelrc index 28695e559f..03d0b13b5b 100644 --- a/src/.bazelrc +++ b/src/.bazelrc @@ -66,7 +66,7 @@ build --incompatible_remove_legacy_whole_archive=False # Escalate Warnings to fail Compile for Thunderbots code build --features=external_include_paths -build:linux --per_file_copt=proto/.*,proto/message_translation/.*,proto/primitive/.*,software/.*,shared/.*,-external/.*@-Wall,-Wextra,-Wno-unused-parameter,-Wno-deprecated,-Werror,-Wno-deprecated-declarations +build --per_file_copt=proto/.*,proto/message_translation/.*,proto/primitive/.*,software/.*,shared/.*,-external/.*@-Wall,-Wextra,-Wno-unused-parameter,-Wno-deprecated,-Werror,-Wno-deprecated-declarations # TODO: #3492 # build --per_file_copt=software/.*,shared/.*,-external/.*@-Wconversion diff --git a/src/MODULE.bazel b/src/MODULE.bazel index c7c7167ef2..7c1597ca68 100644 --- a/src/MODULE.bazel +++ b/src/MODULE.bazel @@ -22,7 +22,6 @@ bazel_dep(name = "rules_proto", version = "7.1.0") bazel_dep(name = "yaml-cpp", version = "0.8.0") bazel_dep(name = "buildifier_prebuilt", version = "8.0.3") bazel_dep(name = "pybind11_protobuf", version = "0.0.0-20250210-f02a2b7") -bazel_dep(name = "bazel_lib", version = "3.1.0") ############################################## # Load PIP packages and our Requirements @@ -269,7 +268,7 @@ new_local_repository( new_local_repository( name = "py_cc_toolchain_host", build_file = "@//extlibs:py_cc_toolchain.BUILD", - path = "/opt/tbotspython/py_headers/include/python3.12/", + path = "/usr/include/python3.12/", ) new_local_repository( diff --git a/src/MODULE.bazel.lock b/src/MODULE.bazel.lock index 180f9e27bf..ce4bc12db9 100644 --- a/src/MODULE.bazel.lock +++ b/src/MODULE.bazel.lock @@ -41,8 +41,6 @@ "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", "https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", - "https://bcr.bazel.build/modules/bazel_lib/3.1.0/MODULE.bazel": "6809765c14e3c766a9b9286c7b0ec56ed87a73326e48fe01749f0c0fdcfe3287", - "https://bcr.bazel.build/modules/bazel_lib/3.1.0/source.json": "aaf7c2dc816219f4cb356c9d65f2555fb7f9543e537199f74a921f7877d23dfb", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", @@ -54,8 +52,7 @@ "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", - "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", - "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/source.json": "7ebaefba0b03efe59cac88ed5bbc67bcf59a3eff33af937345ede2a38b2d368a", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953", "https://bcr.bazel.build/modules/boringssl/0.0.0-20211025-d4f1ab9/MODULE.bazel": "6ee6353f8b1a701fe2178e1d925034294971350b6d3ac37e67e5a7d463267834", "https://bcr.bazel.build/modules/boringssl/0.0.0-20230215-5c22014/MODULE.bazel": "4b03dc0d04375fa0271174badcd202ed249870c8e895b26664fd7298abea7282", "https://bcr.bazel.build/modules/boringssl/0.0.0-20240530-2db0eb3/MODULE.bazel": "d0405b762c5e87cd445b7015f2b8da5400ef9a8dbca0bfefa6c1cea79d528a97", @@ -223,8 +220,7 @@ "https://bcr.bazel.build/modules/rules_rust/0.45.1/MODULE.bazel": "a69d0db3a958fab2c6520961e1b2287afcc8b36690fd31bbc4f6f7391397150d", "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", - "https://bcr.bazel.build/modules/rules_shell/0.4.1/MODULE.bazel": "00e501db01bbf4e3e1dd1595959092c2fadf2087b2852d3f553b5370f5633592", - "https://bcr.bazel.build/modules/rules_shell/0.4.1/source.json": "4757bd277fe1567763991c4425b483477bb82e35e777a56fd846eb5cceda324a", + "https://bcr.bazel.build/modules/rules_shell/0.3.0/source.json": "c55ed591aa5009401ddf80ded9762ac32c358d2517ee7820be981e2de9756cf3", "https://bcr.bazel.build/modules/rules_swift/1.16.0/MODULE.bazel": "4a09f199545a60d09895e8281362b1ff3bb08bbde69c6fc87aff5b92fcc916ca", "https://bcr.bazel.build/modules/rules_swift/2.1.1/MODULE.bazel": "494900a80f944fc7aa61500c2073d9729dff0b764f0e89b824eb746959bc1046", "https://bcr.bazel.build/modules/rules_swift/2.1.1/source.json": "40fc69dfaac64deddbb75bd99cdac55f4427d9ca0afbe408576a65428427a186", diff --git a/src/extlibs/er_force_sim/src/amun/simulator/simulator.cpp b/src/extlibs/er_force_sim/src/amun/simulator/simulator.cpp index 1f64796560..2c00c5a798 100644 --- a/src/extlibs/er_force_sim/src/amun/simulator/simulator.cpp +++ b/src/extlibs/er_force_sim/src/amun/simulator/simulator.cpp @@ -677,13 +677,12 @@ void Simulator::handleSimulatorSetupCommand(const std::unique_ptr if (realism.has_vision_delay()) { - m_visionDelay = std::max(0l, realism.vision_delay()); + m_visionDelay = std::max(0l, realism.vision_delay()); } if (realism.has_vision_processing_time()) { - m_visionProcessingTime = - std::max(0l, realism.vision_processing_time()); + m_visionProcessingTime = std::max(0l, realism.vision_processing_time()); } if (realism.has_simulate_dribbling()) diff --git a/src/software/BUILD b/src/software/BUILD index dbafc6f2e8..dbb2297674 100644 --- a/src/software/BUILD +++ b/src/software/BUILD @@ -1,6 +1,4 @@ -load("@bazel_lib//lib:write_source_files.bzl", "write_source_files") load("@pybind11_bazel//:build_defs.bzl", "pybind_extension", "pybind_library") -load("@rules_pkg//pkg:tar.bzl", "pkg_tar") load("@thunderscope_deps//:requirements.bzl", "requirement") package(default_visibility = ["//visibility:public"]) @@ -29,13 +27,6 @@ cc_binary( ], ) -pkg_tar( - name = "unix_full_system_tar_gen", - srcs = [":unix_full_system"], - extension = "tar.gz", - mode = "0755", -) - cc_binary( name = "er_force_simulator_main", srcs = ["er_force_simulator_main.cpp"], diff --git a/src/software/ai/hl/stp/play/play.cpp b/src/software/ai/hl/stp/play/play.cpp index bdeccf8b35..2d60cde13d 100644 --- a/src/software/ai/hl/stp/play/play.cpp +++ b/src/software/ai/hl/stp/play/play.cpp @@ -207,8 +207,7 @@ std::unique_ptr Play::get( new_primitives_to_assign->robot_primitives()) { primitives_to_run->mutable_robot_primitives()->insert( - google::protobuf::MapPair( - robot_id, primitive)); + google::protobuf::MapPair(robot_id, primitive)); } robots = remaining_robots; diff --git a/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py b/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py index ee0640db37..c362026220 100644 --- a/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py @@ -30,19 +30,15 @@ # test ball very fast misses net (tbots_cpp.Point(0, 0), tbots_cpp.Vector(-5, 1), tbots_cpp.Point(-4.5, 0)), # test ball very fast get saved - # TODO (#3377): This test is flaky due to inconsistent goalie reach. The linked ticket may provide a permanent fix. ( tbots_cpp.Point(-2.5, 0), - # TODO Revert velocity to (-4.8, 1.1) - tbots_cpp.Vector(-3.6, 0.825), + tbots_cpp.Vector(-4.8, 1.1), tbots_cpp.Point(-4.5, 0), ), # test ball very fast with the goalie out of position saved - # TODO (#3377): This test is flaky due to inconsistent goalie reach. The linked ticket may provide a permanent fix. ( tbots_cpp.Point(-2, 0), - # TODO Revert velocity to (-5.5,1) - tbots_cpp.Vector(-4.125, 0.75), + tbots_cpp.Vector(-5.5, 1), tbots_cpp.Point(-4.5, -0.1), ), # ball slow inside friendly defense area diff --git a/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_fsm.cpp b/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_fsm.cpp index b14bb17439..a14599c8a2 100644 --- a/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_fsm.cpp +++ b/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_fsm.cpp @@ -4,7 +4,6 @@ #include "software/ai/evaluation/intercept.h" #include "software/ai/hl/stp/tactic/move_primitive.h" #include "software/geom/algorithms/closest_point.h" -#include "software/geom/algorithms/contains.h" PassDefenderFSM::PassDefenderFSM( std::shared_ptr ai_config_ptr) @@ -19,12 +18,6 @@ bool PassDefenderFSM::passStarted(const Update& event) event.control_params.position_to_block_from.x() - ball_position.x(), event.control_params.position_to_block_from.y() - ball_position.y()); - // Make sure ball is within playing area - if (!contains(event.common.world_ptr->field().fieldLines(), ball_position)) - { - return false; - } - bool pass_started = event.common.world_ptr->ball().hasBallBeenKicked( ball_receiver_point_vector.orientation(), MIN_PASS_SPEED, MAX_PASS_ANGLE_DIFFERENCE); diff --git a/src/software/embedded/BUILD b/src/software/embedded/BUILD index f0fa408d47..f3efd2e985 100644 --- a/src/software/embedded/BUILD +++ b/src/software/embedded/BUILD @@ -110,11 +110,6 @@ sh_binary( srcs = ["setup_robot_software_deps.sh"], ) -filegroup( - name = "hash_thunderloop_binary", - srcs = ["hash_thunderloop_binary.sh"], -) - cc_test( name = "test_battery", srcs = ["battery_test.cpp"], diff --git a/src/software/embedded/ansible/BUILD b/src/software/embedded/ansible/BUILD index 31ec3924a0..f20fb92ee5 100644 --- a/src/software/embedded/ansible/BUILD +++ b/src/software/embedded/ansible/BUILD @@ -18,7 +18,6 @@ py_binary( data = [ ":playbooks", ":tasks", - "//software/embedded:hash_thunderloop_binary.sh", "//software/embedded:setup_robot_software_deps", "//software/embedded:thunderloop_main", "//software/embedded/linux_configs/jetson_nano:jetson_nano_files", diff --git a/src/software/embedded/ansible/tasks/setup_systemd.yml b/src/software/embedded/ansible/tasks/setup_systemd.yml index eeaaa1d16c..2f86fb75b5 100644 --- a/src/software/embedded/ansible/tasks/setup_systemd.yml +++ b/src/software/embedded/ansible/tasks/setup_systemd.yml @@ -15,17 +15,6 @@ dest: ~/thunderbots_binaries/ copy_links: true - - name: Sync Thunderloop MD5 Hash - ansible.posix.synchronize: - src: "{{ playbook_dir }}/../../hash_thunderloop_binary.sh" - dest: "~/hash_thunderloop_binary.sh" - copy_links: true - - - name: Make MD5 Hash Script Executable - ansible.builtin.file: - path: "~/hash_thunderloop_binary.sh" - mode: "0755" - - name: Compute Thunderloop MD5 Hash ansible.builtin.command: "/home/{{ ansible_user }}/hash_thunderloop_binary.sh" register: result diff --git a/src/software/embedded/hash_thunderloop_binary.sh b/src/software/embedded/hash_thunderloop_binary.sh index 4fbb2206d8..8100f6cf3b 100644 --- a/src/software/embedded/hash_thunderloop_binary.sh +++ b/src/software/embedded/hash_thunderloop_binary.sh @@ -14,7 +14,7 @@ a60a083f11289d0904c9c4bf8fa59a59 thunderloop using the awk command filters out only the first argument passed to it, in this case the MD5 hash ' -hash=$(md5sum ~/thunderbots_binaries/thunderloop_main | awk '{ print $1}') +hash=$(md5sum ~/thunderbots_binaries/thunderloop | awk '{ print $1}') run_date=$(date '+%x %H:%M') echo "$hash" > ~/thunderbots_hashes/thunderloop.hash diff --git a/src/software/field_tests/field_test_fixture.py b/src/software/field_tests/field_test_fixture.py index 42c0c9f6ee..829216fde2 100644 --- a/src/software/field_tests/field_test_fixture.py +++ b/src/software/field_tests/field_test_fixture.py @@ -357,7 +357,6 @@ def field_test_runner(): # Launch all binaries with FullSystem( - "software/unix_full_system", full_system_runtime_dir=runtime_dir, debug_full_system=debug_full_sys, friendly_colour_yellow=args.run_yellow, diff --git a/src/software/logger/BUILD b/src/software/logger/BUILD index 6fa65a6909..d10935a852 100644 --- a/src/software/logger/BUILD +++ b/src/software/logger/BUILD @@ -9,7 +9,6 @@ cc_library( "log_merger.h", ], deps = [ - ":compat_flags", "@g3log", ], ) @@ -20,13 +19,9 @@ cc_library( "custom_logging_levels.h", "logger.h", ], - linkopts = select({ - "@platforms//os:linux": ["-lstdc++fs"], - "//conditions:default": [], - }), + linkopts = ["-lstdc++fs"], deps = [ ":coloured_cout_sink", - ":compat_flags", ":csv_sink", ":log_merger", ":plotjuggler_sink", @@ -108,7 +103,6 @@ cc_library( "custom_logging_levels.h", ], deps = [ - ":compat_flags", "@g3log", ], ) @@ -188,7 +182,6 @@ cc_library( "proto_logger.h", ], deps = [ - ":compat_flags", "//proto:tbots_cc_proto", "//software/multithreading:thread_safe_buffer", "@base64", @@ -196,8 +189,3 @@ cc_library( "@zlib", ], ) - -cc_library( - name = "compat_flags", - srcs = ["compat_flags.h"], -) diff --git a/src/software/logger/compat_flags.h b/src/software/logger/compat_flags.h deleted file mode 100644 index c57d8854d2..0000000000 --- a/src/software/logger/compat_flags.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include - -#if defined(__APPLE__) -using Clock = std::chrono::system_clock; -#else -using Clock = std::chrono::_V2::system_clock; -#endif - -#if __cplusplus > 201703L -#include -namespace fs = std::filesystem; -#else -#include -namespace fs = std::experimental::filesystem; -#endif diff --git a/src/software/logger/csv_sink.cpp b/src/software/logger/csv_sink.cpp index 345442d681..07494a78fb 100644 --- a/src/software/logger/csv_sink.cpp +++ b/src/software/logger/csv_sink.cpp @@ -1,6 +1,10 @@ #include "software/logger/csv_sink.h" -#include "compat_flags.h" +#if __cplusplus > 201703L +#include +#else +#include +#endif CSVSink::CSVSink(const std::string& log_directory) : log_directory(log_directory) {} diff --git a/src/software/logger/log_merger.cpp b/src/software/logger/log_merger.cpp index 4139e4fea5..8f82680cfa 100644 --- a/src/software/logger/log_merger.cpp +++ b/src/software/logger/log_merger.cpp @@ -11,7 +11,8 @@ std::list LogMerger::log(g3::LogMessage &log) { std::string msg = log.message(); - Clock::time_point current_time = std::chrono::system_clock::now(); + std::chrono::_V2::system_clock::time_point current_time = + std::chrono::system_clock::now(); // add passed time from testing current_time += passed_time; std::list messages_to_log = _getOldMessages(current_time); @@ -37,7 +38,8 @@ std::list LogMerger::log(g3::LogMessage &log) } } -std::list LogMerger::_getOldMessages(Clock::time_point current_time) +std::list LogMerger::_getOldMessages( + std::chrono::_V2::system_clock::time_point current_time) { std::list result; while (message_list.size() > 0) diff --git a/src/software/logger/log_merger.h b/src/software/logger/log_merger.h index 00a44c02de..2cdbd9d679 100644 --- a/src/software/logger/log_merger.h +++ b/src/software/logger/log_merger.h @@ -5,8 +5,6 @@ #include #include -#include "compat_flags.h" - /** * Handles merging repeated log messages into a single message @@ -43,9 +41,11 @@ class LogMerger * Looks through the message list for expired messages, removes them from the list and * map, and returns them as strings */ - std::list _getOldMessages(Clock::time_point current_time); + std::list _getOldMessages( + std::chrono::_V2::system_clock::time_point current_time); - const Clock::duration LOG_MERGE_DURATION = std::chrono::seconds(2); + const std::chrono::_V2::system_clock::duration LOG_MERGE_DURATION = + std::chrono::seconds(2); private: /** @@ -55,9 +55,10 @@ class LogMerger { g3::LogMessage log; std::string msg; - Clock::time_point timestamp; + std::chrono::_V2::system_clock::time_point timestamp; - Message(g3::LogMessage &log, std::string msg, Clock::time_point timestamp) + Message(g3::LogMessage &log, std::string msg, + std::chrono::_V2::system_clock::time_point timestamp) : log(log), msg(msg), timestamp(timestamp) { } @@ -67,7 +68,8 @@ class LogMerger repeat_map; // maps string messages to their number of repeats for fast access std::list message_list; // used to keep track of time order for messages - Clock::duration passed_time; // for testing, time passed manually + std::chrono::_V2::system_clock::duration + passed_time; // for testing, time passed manually bool enable_merging; }; diff --git a/src/software/logger/logger.h b/src/software/logger/logger.h index df6691750e..d71e6a6a48 100644 --- a/src/software/logger/logger.h +++ b/src/software/logger/logger.h @@ -3,11 +3,12 @@ #include #include +#include +#include #include #include #include -#include "compat_flags.h" #include "software/logger/coloured_cout_sink.h" #include "software/logger/csv_sink.h" #include "software/logger/custom_logging_levels.h" @@ -90,9 +91,9 @@ class LoggerSingleton // hermetic build principles // if log dir doesn't exist, create it - if (!fs::exists(runtime_dir)) + if (!std::experimental::filesystem::exists(runtime_dir)) { - fs::create_directories(runtime_dir); + std::experimental::filesystem::create_directories(runtime_dir); } auto csv_sink_handle = logWorker->addSink(std::make_unique(runtime_dir), diff --git a/src/software/logger/proto_logger.cpp b/src/software/logger/proto_logger.cpp index 4cde56cab7..2362267139 100644 --- a/src/software/logger/proto_logger.cpp +++ b/src/software/logger/proto_logger.cpp @@ -5,13 +5,13 @@ #include #include +#include #include #include #include #include #include "base64.h" -#include "compat_flags.h" #include "shared/constants.h" ProtoLogger::ProtoLogger(const std::string& log_path, @@ -31,7 +31,7 @@ ProtoLogger::ProtoLogger(const std::string& log_path, std::stringstream ss; ss << std::put_time(&tm, REPLAY_FILE_TIME_FORMAT.data()); log_folder_ = log_path_ + "/" + REPLAY_FILE_PREFIX + ss.str() + "/"; - fs::create_directories(log_folder_); + std::experimental::filesystem::create_directories(log_folder_); // Start logging in a separate thread log_thread_ = std::thread(&ProtoLogger::logProtobufs, this); diff --git a/src/software/sensor_fusion/filter/ball_filter.cpp b/src/software/sensor_fusion/filter/ball_filter.cpp index 0967a76ff8..1bee4930f4 100644 --- a/src/software/sensor_fusion/filter/ball_filter.cpp +++ b/src/software/sensor_fusion/filter/ball_filter.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include "shared/constants.h" @@ -263,9 +264,17 @@ BallFilter::LinearRegressionResults BallFilter::calculateLinearRegression( A.bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b); // How to calculate the error is from // https://eigen.tuxfamily.org/dox/group__TutorialLinearAlgebra.html - // NOTE: using absolute error instead of relative because coordinates - // values should not affect error, also handles divide by 0 error - double regression_error = (A * regression_vector - b).norm(); // norm() is L2 norm + double regression_error = std::numeric_limits::max(); + + if ((A * regression_vector - b).norm() == 0 && b.norm() == 0) + { + regression_error = 0; + } + if (b.norm() != 0) + { + regression_error = + (A * regression_vector - b).norm() / (b.norm()); // norm() is L2 norm + } // Find 2 points on the regression line that we solved for, and use this to construct // our own Line class diff --git a/src/software/sensor_fusion/filter/ball_filter.h b/src/software/sensor_fusion/filter/ball_filter.h index cf108b2ac6..678bef7ae3 100644 --- a/src/software/sensor_fusion/filter/ball_filter.h +++ b/src/software/sensor_fusion/filter/ball_filter.h @@ -43,8 +43,7 @@ class BallFilter static constexpr double MAX_BUFFER_SIZE_VELOCITY_MAGNITUDE = 4.0; // The extra amount beyond the ball's max speed that we treat ball detections as valid static constexpr double MAX_ACCEPTABLE_BALL_SPEED_BUFFER = 2.0; - // The maximum root mean squared error threshold to considering using the generated - // linear regression. + // The maximum error threshold to considering using the generated linear regression // TODO (#2752): Investigate different values of error threshold static constexpr double LINEAR_REGRESSION_ERROR_THRESHOLD = 1000.0; @@ -86,7 +85,6 @@ class BallFilter struct LinearRegressionResults { Line regression_line; - // Regression error is root mean squared error double regression_error; }; @@ -133,8 +131,7 @@ class BallFilter /** * Given a buffer of ball detections, returns the line of best fit through - * the detection positions, and calculate the root mean squared error of this - * regression. + * the detection positions, and calculate the error of this regression. * Note: also considers vertical lines. * * @throws std::invalid_argument if ball_detections has less than 2 elements @@ -148,8 +145,7 @@ class BallFilter /** * Given a list of ball detections, use linear regression to find a line of best fit - * through the ball positions, and calculate the root mean squared error of this - * regression. + * through the ball positions, and calculate the error of this regression. * * @throws std::invalid_argument if ball_detections has less than 2 elements * diff --git a/src/software/simulated_tests/simulated_test_fixture.py b/src/software/simulated_tests/simulated_test_fixture.py index ad2d319dbb..10819a1a00 100644 --- a/src/software/simulated_tests/simulated_test_fixture.py +++ b/src/software/simulated_tests/simulated_test_fixture.py @@ -520,14 +520,12 @@ def simulated_test_runner(): args.debug_simulator, args.enable_realism, ) as simulator, FullSystem( - "software/unix_full_system", f"{args.blue_full_system_runtime_dir}/test/{test_name}", args.debug_blue_full_system, False, should_restart_on_crash=False, running_in_realtime=args.enable_thunderscope, ) as blue_fs, FullSystem( - "software/unix_full_system", f"{args.yellow_full_system_runtime_dir}/test/{test_name}", args.debug_yellow_full_system, True, diff --git a/src/software/thunderscope/BUILD b/src/software/thunderscope/BUILD index 2e7fddb8de..464997efca 100644 --- a/src/software/thunderscope/BUILD +++ b/src/software/thunderscope/BUILD @@ -6,7 +6,6 @@ package(default_visibility = ["//visibility:public"]) compile_pip_requirements( name = "requirements", src = "requirements.in", - requirements_darwin = "requirements_lock.darwin.txt", requirements_txt = "requirements_lock.txt", ) @@ -21,7 +20,6 @@ py_binary( ":util", "//software/thunderscope/binary_context_managers:full_system", "//software/thunderscope/binary_context_managers:game_controller", - "//software/thunderscope/binary_context_managers:runtime_manager", "//software/thunderscope/binary_context_managers:simulator", ], ) diff --git a/src/software/thunderscope/binary_context_managers/BUILD b/src/software/thunderscope/binary_context_managers/BUILD index 0fe462dd43..e7805dad6f 100644 --- a/src/software/thunderscope/binary_context_managers/BUILD +++ b/src/software/thunderscope/binary_context_managers/BUILD @@ -22,7 +22,6 @@ py_library( deps = [ "//proto:import_all_protos", "//software/networking:ssl_proto_communication", - "//software/thunderscope:util", "//software/thunderscope/common:thread_safe_circular_buffer", ], ) @@ -51,12 +50,3 @@ py_library( "//software/thunderscope:time_provider", ], ) - -py_library( - name = "runtime_manager", - srcs = [ - "runtime_installer.py", - "runtime_loader.py", - "runtime_manager.py", - ], -) diff --git a/src/software/thunderscope/binary_context_managers/full_system.py b/src/software/thunderscope/binary_context_managers/full_system.py index 8e8abc4e48..5fc0bad397 100644 --- a/src/software/thunderscope/binary_context_managers/full_system.py +++ b/src/software/thunderscope/binary_context_managers/full_system.py @@ -1,20 +1,18 @@ from __future__ import annotations -import logging import os -import threading +import logging import time -from subprocess import Popen, TimeoutExpired +import threading -from software.py_constants import * +from subprocess import Popen, TimeoutExpired +from software.thunderscope.gl.layers.gl_obstacle_layer import ObstacleList +from software.thunderscope.proto_unix_io import ProtoUnixIO from software.python_bindings import * - from proto.import_all_protos import * from software.py_constants import * from software.thunderscope.constants import LogLevels from software.thunderscope.binary_context_managers.util import is_cmd_running -from software.thunderscope.gl.layers.gl_obstacle_layer import ObstacleList -from software.thunderscope.proto_unix_io import ProtoUnixIO class FullSystem: @@ -22,7 +20,6 @@ class FullSystem: def __init__( self, - path_to_binary: str, full_system_runtime_dir: os.PathLike = None, debug_full_system: bool = False, friendly_colour_yellow: bool = False, @@ -33,7 +30,6 @@ def __init__( ) -> None: """Run FullSystem - :param path_to_binary: The path of the binary used for this unix full system :param full_system_runtime_dir: The directory to run the blue full_system in :param debug_full_system: Whether to run the full_system in debug mode :param friendly_color_yellow: a argument passed into the unix_full_system binary (--friendly_colour_yellow) @@ -42,7 +38,6 @@ def __init__( :param running_in_realtime: True if we are running fullsystem in realtime, else False :param log_level: Minimum g3log level that will be printed (DEBUG|INFO|WARNING|FATAL) """ - self.path_to_binary = path_to_binary self.full_system_runtime_dir = full_system_runtime_dir self.debug_full_system = debug_full_system self.friendly_colour_yellow = friendly_colour_yellow @@ -51,6 +46,7 @@ def __init__( self.should_run_under_sudo = run_sudo self.running_in_realtime = running_in_realtime self.log_level = log_level + self.thread = threading.Thread(target=self.__restart__, daemon=True) def __enter__(self) -> FullSystem: @@ -69,12 +65,13 @@ def __enter__(self) -> FullSystem: except: pass - self.full_system = "{} --runtime_dir={} {} {} --log_level={}".format( - self.path_to_binary, - self.full_system_runtime_dir, - "--friendly_colour_yellow" if self.friendly_colour_yellow else "", - "--ci" if not self.running_in_realtime else "", - self.log_level.value, + self.full_system = ( + "software/unix_full_system --runtime_dir={} {} {} --log_level={}".format( + self.full_system_runtime_dir, + "--friendly_colour_yellow" if self.friendly_colour_yellow else "", + "--ci" if not self.running_in_realtime else "", + self.log_level.value, + ) ) if self.should_run_under_sudo: diff --git a/src/software/thunderscope/binary_context_managers/game_controller.py b/src/software/thunderscope/binary_context_managers/game_controller.py index 79cc8a0925..20c8f11033 100644 --- a/src/software/thunderscope/binary_context_managers/game_controller.py +++ b/src/software/thunderscope/binary_context_managers/game_controller.py @@ -4,6 +4,7 @@ import random import logging import os +import socket import time from subprocess import Popen from typing import Any @@ -20,7 +21,6 @@ from software.thunderscope.common.thread_safe_circular_buffer import ( ThreadSafeCircularBuffer, ) -from software.thunderscope.util import is_current_platform_macos logger = logging.getLogger(__name__) import itertools @@ -288,15 +288,10 @@ def __send_referee_command(data: Referee) -> None: if autoref_proto_unix_io is not None: autoref_proto_unix_io.send_proto(Referee, data) - if is_current_platform_macos(): - loopback_iface = "en0" - else: - loopback_iface = "lo" - self.receive_referee_command = tbots_cpp.SSLRefereeProtoListener( Gamecontroller.REFEREE_IP, self.referee_port, - loopback_iface, + "lo", __send_referee_command, True, ) diff --git a/src/software/thunderscope/binary_context_managers/runtime_installer.py b/src/software/thunderscope/binary_context_managers/runtime_installer.py deleted file mode 100644 index 952eb80613..0000000000 --- a/src/software/thunderscope/binary_context_managers/runtime_installer.py +++ /dev/null @@ -1,67 +0,0 @@ -import requests -from pathlib import Path -import tarfile -import shutil -import platform -from software.thunderscope.constants import RuntimeManagerConstants - - -class RuntimeInstaller: - """Delegate class for handling runtime installation and remote interfacing""" - - def __init__(self): - self.runtime_install_targets = {} - - def fetch_remote_runtimes(self) -> list[str]: - """Requests a list of available runtimes from the remote. This is an expensive operation - and should only be called when necessary. - :return: A unique list of names for available runtimes - """ - releases = requests.get( - RuntimeManagerConstants.RELEASES_URL, - headers={"Accept": "application/vnd.github+json"}, - ).json() - - version_names = [] - - # Currently the only targets that are supported for each os - os_to_target = {"Darwin": "mac-arm64", "Linux": "ubuntu-x86"} - target = os_to_target[platform.system()] - - for release in releases: - version = release["tag_name"] - for asset in release.get("assets", []): - url = asset["browser_download_url"] - - if "unix_full_system" in url and target in url: - version_names.append(version) - self.runtime_install_targets[version] = url - - return version_names[: RuntimeManagerConstants.MAX_RELEASES_FETCHED] - - def install_runtime(self, version: str) -> None: - """Installs the runtime of the specified version or throws an error upon failure. - Ensures that the runtime is compatible with the current platform - :param version: Version of the runtime hosted on the remote to install - """ - url = self.runtime_install_targets[version] - - filename = Path(url).name - target_dir = Path(RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH) - tmp_dir = Path("/tmp") - tmp_path = tmp_dir / filename - extracted_binary_name = "unix_full_system" - - with requests.get(url, stream=True) as r: - r.raise_for_status() - with open(tmp_path, "wb") as f: - for chunk in r.iter_content(chunk_size=8192): - if chunk: - f.write(chunk) - - dest = target_dir / f"{extracted_binary_name}_{version}" - - # Our release assets for FullSystem are always tar.gz files - with tarfile.open(tmp_path, "r:*") as tar: - tar.extractall(tmp_dir) - shutil.move(tmp_dir / extracted_binary_name, dest) diff --git a/src/software/thunderscope/binary_context_managers/runtime_loader.py b/src/software/thunderscope/binary_context_managers/runtime_loader.py deleted file mode 100644 index 49208a7b17..0000000000 --- a/src/software/thunderscope/binary_context_managers/runtime_loader.py +++ /dev/null @@ -1,139 +0,0 @@ -from tomllib import TOMLDecodeError -from software.thunderscope.constants import RuntimeManagerConstants -import os -import tomllib -import logging - - -class RuntimeConfig: - """Class to store the names and get paths of the two binaries""" - - def __init__( - self, - blue_runtime: str | None = None, - yellow_runtime: str | None = None, - ) -> None: - """Create runtime config, replacing invalid runtimes with default FullSystem - :param blue_runtime: blue runtime name, None if default - :param yellow_runtime: yellow runtime name, None if default - """ - self.blue_runtime = ( - blue_runtime - if blue_runtime - and self._is_valid_runtime_path(self._get_runtime_path(blue_runtime)) - else RuntimeManagerConstants.DEFAULT_BINARY_NAME - ) - self.yellow_runtime = ( - yellow_runtime - if yellow_runtime - and self._is_valid_runtime_path(self._get_runtime_path(yellow_runtime)) - else RuntimeManagerConstants.DEFAULT_BINARY_NAME - ) - - def get_blue_runtime_path(self) -> str: - """Returns the path of the stored yellow runtime - :return: the absolute path of the binary as a string, or the relative path of our FullSystem - """ - return self._get_runtime_path(self.blue_runtime) - - def get_yellow_runtime_path(self) -> str: - """Returns the path of the stored yellow runtime - :return: the absolute path of the binary as a string, or the relative path of our FullSystem - """ - return self._get_runtime_path(self.yellow_runtime) - - def _get_runtime_path(self, selected_runtime: str) -> str: - """Gets the absolute path of a binary given its name, or the path of our default FullSystem - if the binary is not valid. - :param selected_runtime: the name of the selected runtime binary - :return: the absolute path of the binary as a string, or the relative path of our FullSystem - """ - file_path = os.path.join( - RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH, selected_runtime - ) - # Default to local FullSystem if it is selected or the selected binary isn't a valid runtime - if ( - selected_runtime == RuntimeManagerConstants.DEFAULT_BINARY_NAME - or not self._is_valid_runtime_path(file_path) - ): - return RuntimeManagerConstants.DEFAULT_BINARY_PATH - # Remove leading and trailing white space and return - return file_path.strip() - - def _is_valid_runtime_path(self, runtime_path: str) -> bool: - """Returns if the binary exists and if it is an executable. - :param runtime_path: the path to check - :return: True if it is a valid runtime - """ - return os.path.isfile(runtime_path) and os.access(runtime_path, os.X_OK) - - -class RuntimeLoader: - """Delegate class for handling local runtimes and managing runtime selection""" - - def fetch_installed_runtimes(self) -> list[str]: - """Fetches the list of installed runtimes from the local disk. - Creates the external runtimes directory in our local disk if it does not exist yet. - :return: A list of installed runtime names - """ - runtime_list = [RuntimeManagerConstants.DEFAULT_BINARY_NAME] - - if not os.path.isdir(RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH): - os.mkdir(RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH) - - # Check for all executable files in the directory, and add its name to the list - for file_name in os.listdir(RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH): - file_path = os.path.join( - RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH, file_name - ) - if os.access(file_path, os.X_OK): - runtime_list.append(file_name) - - return runtime_list - - def load_selected_runtimes(self, yellow_runtime: str, blue_runtime: str) -> None: - """Loads the yellow and blue runtimes specified by saving them in the local disk. - :param blue_runtime: name of the blue runtime to set - :param yellow_runtime: name of the yellow runtime to set - """ - # Format as TOML - selected_runtimes = ( - f'{RuntimeManagerConstants.RUNTIME_CONFIG_BLUE_KEY} = "{blue_runtime}"\n' - f'{RuntimeManagerConstants.RUNTIME_CONFIG_YELLOW_KEY} = "{yellow_runtime}"' - ) - - # create a new config file if it doesn't exist, and write in the format above to it - with open(RuntimeManagerConstants.RUNTIME_CONFIG_PATH, "w") as file: - file.write(selected_runtimes) - - def fetch_runtime_config(self) -> RuntimeConfig: - """Fetches the runtime configuration from the local disk, creating it if it doesn't exist. - :return: Returns the runtime configuration as a RuntimeConfig - """ - # Create empty config file if doesn't exist yet - os.makedirs( - os.path.dirname(RuntimeManagerConstants.RUNTIME_CONFIG_PATH), exist_ok=True - ) - open(RuntimeManagerConstants.RUNTIME_CONFIG_PATH, "a").close() - - try: - with open(RuntimeManagerConstants.RUNTIME_CONFIG_PATH, "rb") as file: - selected_runtime_dict = tomllib.load(file) - - # Get the persisted runtimes - config = RuntimeConfig( - selected_runtime_dict.get( - RuntimeManagerConstants.RUNTIME_CONFIG_BLUE_KEY - ), - selected_runtime_dict.get( - RuntimeManagerConstants.RUNTIME_CONFIG_YELLOW_KEY - ), - ) - - return config - except TOMLDecodeError: - logging.warning( - f"Failed to read TOML file at: {RuntimeManagerConstants.RUNTIME_CONFIG_PATH}" - ) - - return RuntimeConfig() diff --git a/src/software/thunderscope/binary_context_managers/runtime_manager.py b/src/software/thunderscope/binary_context_managers/runtime_manager.py deleted file mode 100644 index 3316ec0372..0000000000 --- a/src/software/thunderscope/binary_context_managers/runtime_manager.py +++ /dev/null @@ -1,49 +0,0 @@ -from software.thunderscope.binary_context_managers.runtime_installer import ( - RuntimeInstaller, -) -from software.thunderscope.binary_context_managers.runtime_loader import ( - RuntimeLoader, - RuntimeConfig, -) - - -class RuntimeManager: - """Class for interfacing with AI runtimes/backends (full system or external) on the disk""" - - def __init__(self): - self.runtime_installer = RuntimeInstaller() - self.runtime_loader = RuntimeLoader() - - def fetch_remote_runtimes(self) -> list[str]: - """Requests a list of available runtimes from the remote. Includes DEFAULT_BINARY_NAME by default - :return: A unique list of names for available runtimes - """ - return self.runtime_installer.fetch_remote_runtimes() - - def install_runtime(self, version: str) -> None: - """Installs the runtime of the specified version or throws an error upon failure - :param version: Version of the runtime hosted on the remote to install - """ - self.runtime_installer.install_runtime(version) - - def fetch_installed_runtimes(self) -> list[str]: - """Fetches the list of available runtimes from the local disk - :return: A list of names for available runtimes - """ - return self.runtime_loader.fetch_installed_runtimes() - - def load_selected_runtimes(self, yellow_runtime: str, blue_runtime: str) -> None: - """Loads the runtimes into the runtime loader config file on the local disk - :param blue_runtime: name of the blue runtime to load - :param yellow_runtime: name of the yellow runtime to load - """ - self.runtime_loader.load_selected_runtimes(yellow_runtime, blue_runtime) - - def fetch_runtime_config(self) -> RuntimeConfig: - """Fetches the runtime configuration from the local disk - :return: Returns the runtime configuration as a RuntimeConfig - """ - return self.runtime_loader.fetch_runtime_config() - - -runtime_manager_instance = RuntimeManager() diff --git a/src/software/thunderscope/constants.py b/src/software/thunderscope/constants.py index 4ba2612e56..12cb5b0d83 100644 --- a/src/software/thunderscope/constants.py +++ b/src/software/thunderscope/constants.py @@ -393,17 +393,3 @@ class LogLevels(StrEnum): INFO = "INFO" WARNING = "WARNING" FATAL = "FATAL" - - -class RuntimeManagerConstants: - """Constants for Runtime Manager""" - - RUNTIME_CONFIG_BLUE_KEY = "blue_runtime_name" - RUNTIME_CONFIG_YELLOW_KEY = "yellow_runtime_name" - DEFAULT_BINARY_PATH = "software/unix_full_system" - DEFAULT_BINARY_NAME = "Current Fullsystem" - EXTERNAL_RUNTIMES_PATH = "/opt/tbotspython/external_runtimes" - RUNTIME_CONFIG_PATH = f"{EXTERNAL_RUNTIMES_PATH}/runtime_config.toml" - RELEASES_URL = "https://api.github.com/repos/UBC-Thunderbots/Software/releases" - DOWNLOAD_URL = "https://github.com/UBC-Thunderbots/Software/releases/download/" - MAX_RELEASES_FETCHED = 5 diff --git a/src/software/thunderscope/gl/widgets/BUILD b/src/software/thunderscope/gl/widgets/BUILD index 1033dacfe6..7526278ced 100644 --- a/src/software/thunderscope/gl/widgets/BUILD +++ b/src/software/thunderscope/gl/widgets/BUILD @@ -2,24 +2,6 @@ load("@thunderscope_deps//:requirements.bzl", "requirement") package(default_visibility = ["//visibility:public"]) -py_library( - name = "gl_runtime_installer", - srcs = ["gl_runtime_installer.py"], - deps = [ - "//software/thunderscope/binary_context_managers:runtime_manager", - requirement("pyqtgraph"), - ], -) - -py_library( - name = "gl_runtime_selector", - srcs = ["gl_runtime_selector.py"], - deps = [ - "//software/thunderscope/binary_context_managers:runtime_manager", - requirement("pyqtgraph"), - ], -) - py_library( name = "gl_toolbar", srcs = ["gl_toolbar.py"], @@ -51,8 +33,6 @@ py_library( name = "gl_gamecontroller_toolbar", srcs = ["gl_gamecontroller_toolbar.py"], deps = [ - ":gl_runtime_installer", - ":gl_runtime_selector", ":gl_toolbar", "//proto:import_all_protos", "//software/networking:ssl_proto_communication", diff --git a/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py b/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py index d1aed796fd..042b6622e8 100644 --- a/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py @@ -4,12 +4,8 @@ from proto.ssl_gc_common_pb2 import Team as SslTeam from typing import Callable, override import webbrowser -from software.thunderscope.gl.widgets.gl_runtime_selector import GLRuntimeSelectorDialog from software.thunderscope.gl.widgets.gl_toolbar import GLToolbar from software.thunderscope.proto_unix_io import ProtoUnixIO -from software.thunderscope.gl.widgets.gl_runtime_installer import ( - GLRuntimeInstallerDialog, -) import qtawesome as qta @@ -99,20 +95,6 @@ def __init__( display_text="Open GC", ) - self.runtime_installer_button = self.__setup_icon_button( - qta.icon("mdi6.download"), - "Opens a runtime installer modal", - self.__open_runtime_installer_dialog, - display_text="Install Runtimes", - ) - - self.runtime_selector_button = self.__setup_icon_button( - qta.icon("mdi6.server"), - "Select runtimes for each team", - self.__open_runtime_selector_dialog, - display_text="Select Runtimes", - ) - # disable the normal start button when no play is selected self.normal_start_enabled = True self.__toggle_normal_start_button() @@ -128,9 +110,6 @@ def __init__( self.__add_separator(self.layout()) self.layout().addWidget(self.gc_browser_button) self.layout().addStretch() - self.__add_separator(self.layout()) - self.layout().addWidget(self.runtime_installer_button) - self.layout().addWidget(self.runtime_selector_button) @override def refresh(self) -> None: @@ -269,17 +248,3 @@ def __send_gc_command(self, command: Command.Type, team: Team) -> None: """ command = ManualGCCommand(manual_command=Command(type=command, for_team=team)) self.proto_unix_io.send_proto(ManualGCCommand, command) - - def __open_runtime_installer_dialog(self) -> None: - """Opens the runtime installer modal, initializing if first time""" - if not hasattr(self, "runtime_installer_dialog"): - self.runtime_installer_dialog = GLRuntimeInstallerDialog( - parent=self.parent() - ) - - self.runtime_installer_dialog.show() - - def __open_runtime_selector_dialog(self) -> None: - """Initializes and opens the runtime selector dialog""" - self.runtime_selector_dialog = GLRuntimeSelectorDialog(parent=self.parent()) - self.runtime_selector_dialog.show() diff --git a/src/software/thunderscope/gl/widgets/gl_runtime_installer.py b/src/software/thunderscope/gl/widgets/gl_runtime_installer.py deleted file mode 100644 index f0facfc5f5..0000000000 --- a/src/software/thunderscope/gl/widgets/gl_runtime_installer.py +++ /dev/null @@ -1,56 +0,0 @@ -from pyqtgraph.Qt.QtWidgets import ( - QDialog, - QWidget, - QPushButton, - QListWidget, - QAbstractItemView, - QVBoxLayout, -) - -from software.thunderscope.binary_context_managers.runtime_manager import ( - runtime_manager_instance, -) - - -class GLRuntimeInstallerDialog(QDialog): - """Modal that displays selectable list of runtimes to install""" - - def __init__(self, parent: QWidget): - """Initializes runtime installer modal, fetching a list of installable - runtimes and adding to a selectable list - - :param parent: the modal's parent - """ - super().__init__(parent) - - runtimes = runtime_manager_instance.fetch_remote_runtimes() - - self.setWindowTitle("Install runtimes") - self.setModal(True) - self.setMinimumWidth(400) - - install_button = QPushButton("Install") - install_button.clicked.connect(self.__install_selected_runtimes) - - runtime_select_list = QListWidget() - runtime_select_list.setSelectionMode( - QAbstractItemView.SelectionMode.MultiSelection - ) - runtime_select_list.setFixedHeight(200) - runtime_select_list.addItems(runtimes) - - self.runtime_select_list = runtime_select_list - - layout = QVBoxLayout(self) - layout.addWidget(runtime_select_list) - layout.addWidget(install_button) - - def __install_selected_runtimes(self) -> None: - """Installs all runtimes that are currently selected""" - selected_items = self.runtime_select_list.selectedItems() - selected_runtimes = [item.text() for item in selected_items] - - for runtime in selected_runtimes: - runtime_manager_instance.install_runtime(runtime) - - self.close() diff --git a/src/software/thunderscope/gl/widgets/gl_runtime_selector.py b/src/software/thunderscope/gl/widgets/gl_runtime_selector.py deleted file mode 100644 index 1f84f3d835..0000000000 --- a/src/software/thunderscope/gl/widgets/gl_runtime_selector.py +++ /dev/null @@ -1,100 +0,0 @@ -from pyqtgraph.Qt.QtWidgets import ( - QDialog, - QWidget, - QVBoxLayout, - QHBoxLayout, - QLabel, - QPushButton, - QComboBox, -) - -from software.thunderscope.binary_context_managers.runtime_manager import ( - runtime_manager_instance, -) - - -class GLRuntimeSelectorDialog(QDialog): - """Modal that displays the selectable list of runtimes for blue and yellow teams""" - - def __init__(self, parent: QWidget): - """Initializes the runtime selector modal, displaying the same list of installed - runtimes for both the blue and yellow teams. - - :param parent: the modal's parent - """ - super().__init__(parent) - - runtime_options = runtime_manager_instance.fetch_installed_runtimes() - runtime_config = runtime_manager_instance.fetch_runtime_config() - - # Put selected runtimes from config at start of list - blue_runtimes = [runtime_config.blue_runtime] + [ - x for x in runtime_options if x != runtime_config.blue_runtime - ] - yellow_runtimes = [runtime_config.yellow_runtime] + [ - x for x in runtime_options if x != runtime_config.yellow_runtime - ] - - self.setWindowTitle("Select Runtimes") - self.setModal(True) - self.setMinimumWidth(400) - - layout = QVBoxLayout(self) - - # Blue runtime - layout.addWidget(QLabel("Blue Runtime")) - self.blue_menu = QComboBox() - self.blue_menu.addItems(blue_runtimes) - self.blue_menu.currentTextChanged.connect(self._on_blue_changed) - self._blue_selection = self.blue_menu.currentText() - layout.addWidget(self.blue_menu) - - layout.addSpacing(10) - - # Yellow runtime - layout.addWidget(QLabel("Yellow Runtime")) - self.yellow_menu = QComboBox() - self.yellow_menu.addItems(yellow_runtimes) - self.yellow_menu.currentTextChanged.connect(self._on_yellow_changed) - self._yellow_selection = self.yellow_menu.currentText() - layout.addWidget(self.yellow_menu) - - # Restart note - layout.addSpacing(15) - restart_note = QLabel( - "Note: Restart Thunderscope for changes to take effect." - ) - layout.addWidget(restart_note) - - # Done button - layout.addSpacing(15) - button_row = QHBoxLayout() - button_row.addStretch() - - done_button = QPushButton("Done") - done_button.clicked.connect(self._on_done) - button_row.addWidget(done_button) - - layout.addLayout(button_row) - - def _on_blue_changed(self, value: str) -> None: - """Stores currently selected runtime for blue team - - :param value: the value of the selected option - """ - self._blue_selection = value - - def _on_yellow_changed(self, value: str) -> None: - """Stores currently selected runtime for yellow team - - :param value: the value of the selected option - """ - self._yellow_selection = value - - def _on_done(self) -> None: - """Commits the selected runtimes and closes the modal.""" - runtime_manager_instance.load_selected_runtimes( - self._yellow_selection, self._blue_selection - ) - - self.close() diff --git a/src/software/thunderscope/requirements.in b/src/software/thunderscope/requirements.in index 18c98bc70f..32a0054344 100644 --- a/src/software/thunderscope/requirements.in +++ b/src/software/thunderscope/requirements.in @@ -1,6 +1,6 @@ colorama==0.4.6 netifaces==0.11.0 -evdev==1.7.0; sys_platform == "linux" +evdev==1.7.0 numpy==1.26.4 protobuf==6.31.1 pyqtgraph==0.13.7 diff --git a/src/software/thunderscope/requirements_lock.darwin.txt b/src/software/thunderscope/requirements_lock.darwin.txt deleted file mode 100644 index 87532ad2ba..0000000000 --- a/src/software/thunderscope/requirements_lock.darwin.txt +++ /dev/null @@ -1,126 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# bazel run //software/thunderscope:requirements.update -# -colorama==0.4.6 \ - --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ - --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 - # via -r software/thunderscope/requirements.in -darkdetect==0.7.1 \ - --hash=sha256:3efe69f8ecd5f1b7f4fbb0d1d93f656b0e493c45cc49222380ffe2a529cbc866 \ - --hash=sha256:47be3cf5134432ddb616bbffc927237718407914993c82809983e7ccebf49013 - # via pyqtdarktheme-fork -netifaces==0.11.0 \ - --hash=sha256:043a79146eb2907edf439899f262b3dfe41717d34124298ed281139a8b93ca32 \ - --hash=sha256:08e3f102a59f9eaef70948340aeb6c89bd09734e0dca0f3b82720305729f63ea \ - --hash=sha256:0f6133ac02521270d9f7c490f0c8c60638ff4aec8338efeff10a1b51506abe85 \ - --hash=sha256:18917fbbdcb2d4f897153c5ddbb56b31fa6dd7c3fa9608b7e3c3a663df8206b5 \ - --hash=sha256:2479bb4bb50968089a7c045f24d120f37026d7e802ec134c4490eae994c729b5 \ - --hash=sha256:2650beee182fed66617e18474b943e72e52f10a24dc8cac1db36c41ee9c041b7 \ - --hash=sha256:28f4bf3a1361ab3ed93c5ef360c8b7d4a4ae060176a3529e72e5e4ffc4afd8b0 \ - --hash=sha256:3ecb3f37c31d5d51d2a4d935cfa81c9bc956687c6f5237021b36d6fdc2815b2c \ - --hash=sha256:469fc61034f3daf095e02f9f1bbac07927b826c76b745207287bc594884cfd05 \ - --hash=sha256:48324183af7f1bc44f5f197f3dad54a809ad1ef0c78baee2c88f16a5de02c4c9 \ - --hash=sha256:50721858c935a76b83dd0dd1ab472cad0a3ef540a1408057624604002fcfb45b \ - --hash=sha256:54ff6624eb95b8a07e79aa8817288659af174e954cca24cdb0daeeddfc03c4ff \ - --hash=sha256:5be83986100ed1fdfa78f11ccff9e4757297735ac17391b95e17e74335c2047d \ - --hash=sha256:5f9ca13babe4d845e400921973f6165a4c2f9f3379c7abfc7478160e25d196a4 \ - --hash=sha256:73ff21559675150d31deea8f1f8d7e9a9a7e4688732a94d71327082f517fc6b4 \ - --hash=sha256:7dbb71ea26d304e78ccccf6faccef71bb27ea35e259fb883cfd7fd7b4f17ecb1 \ - --hash=sha256:815eafdf8b8f2e61370afc6add6194bd5a7252ae44c667e96c4c1ecf418811e4 \ - --hash=sha256:841aa21110a20dc1621e3dd9f922c64ca64dd1eb213c47267a2c324d823f6c8f \ - --hash=sha256:84e4d2e6973eccc52778735befc01638498781ce0e39aa2044ccfd2385c03246 \ - --hash=sha256:8f7da24eab0d4184715d96208b38d373fd15c37b0dafb74756c638bd619ba150 \ - --hash=sha256:96c0fe9696398253f93482c84814f0e7290eee0bfec11563bd07d80d701280c3 \ - --hash=sha256:aab1dbfdc55086c789f0eb37affccf47b895b98d490738b81f3b2360100426be \ - --hash=sha256:c03fb2d4ef4e393f2e6ffc6376410a22a3544f164b336b3a355226653e5efd89 \ - --hash=sha256:c37a1ca83825bc6f54dddf5277e9c65dec2f1b4d0ba44b8fd42bc30c91aa6ea1 \ - --hash=sha256:c92ff9ac7c2282009fe0dcb67ee3cd17978cffbe0c8f4b471c00fe4325c9b4d4 \ - --hash=sha256:c9a3a47cd3aaeb71e93e681d9816c56406ed755b9442e981b07e3618fb71d2ac \ - --hash=sha256:cb925e1ca024d6f9b4f9b01d83215fd00fe69d095d0255ff3f64bffda74025c8 \ - --hash=sha256:d07b01c51b0b6ceb0f09fc48ec58debd99d2c8430b09e56651addeaf5de48048 \ - --hash=sha256:e76c7f351e0444721e85f975ae92718e21c1f361bda946d60a214061de1f00a1 \ - --hash=sha256:eb4813b77d5df99903af4757ce980a98c4d702bbcb81f32a0b305a1537bdf0b1 - # via -r software/thunderscope/requirements.in -numpy==1.26.4 \ - --hash=sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b \ - --hash=sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818 \ - --hash=sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20 \ - --hash=sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0 \ - --hash=sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010 \ - --hash=sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a \ - --hash=sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea \ - --hash=sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c \ - --hash=sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71 \ - --hash=sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110 \ - --hash=sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be \ - --hash=sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a \ - --hash=sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a \ - --hash=sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5 \ - --hash=sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed \ - --hash=sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd \ - --hash=sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c \ - --hash=sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e \ - --hash=sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0 \ - --hash=sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c \ - --hash=sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a \ - --hash=sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b \ - --hash=sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0 \ - --hash=sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6 \ - --hash=sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2 \ - --hash=sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a \ - --hash=sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30 \ - --hash=sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218 \ - --hash=sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5 \ - --hash=sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07 \ - --hash=sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2 \ - --hash=sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4 \ - --hash=sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764 \ - --hash=sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef \ - --hash=sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3 \ - --hash=sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f - # via - # -r software/thunderscope/requirements.in - # pyqtgraph -packaging==24.2 \ - --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ - --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f - # via qtpy -protobuf==6.31.1 \ - --hash=sha256:0414e3aa5a5f3ff423828e1e6a6e907d6c65c1d5b7e6e975793d5590bdeecc16 \ - --hash=sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447 \ - --hash=sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6 \ - --hash=sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402 \ - --hash=sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e \ - --hash=sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9 \ - --hash=sha256:8764cf4587791e7564051b35524b72844f845ad0bb011704c3736cce762d8fe9 \ - --hash=sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39 \ - --hash=sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a - # via -r software/thunderscope/requirements.in -pyqt-toast-notification==1.3.2 \ - --hash=sha256:135736ec0f16bff41104dee3c60ac318e5d55ae3378bf26892c6d08c36088ae6 \ - --hash=sha256:82688101202737736d51ab6c74a573b32266ecb7c8b0002f913407bd369737d9 - # via -r software/thunderscope/requirements.in -pyqt6-qt6==6.8.1 \ - --hash=sha256:006d786693d0511fbcf184a862edbd339c6ed1bb3bd9de363d73a19ed4b23dff \ - --hash=sha256:08065d595f1e6fc2dde9f4450eeff89082f4bad26f600a8e9b9cc5966716bfcf \ - --hash=sha256:1eb8460a1fdb38d0b2458c2974c01d471c1e59e4eb19ea63fc447aaba3ad530e \ - --hash=sha256:20843cb86bd94942d1cd99e39bf1aeabb875b241a35a8ab273e4bbbfa63776db \ - --hash=sha256:9f3790c4ce4dc576e48b8718d55fb8743057e6cbd53a6ca1dd253ffbac9b7287 \ - --hash=sha256:a8bc2ed4ee5e7c6ff4dd1c7db0b27705d151fee5dc232bbd1bf17618f937f515 \ - --hash=sha256:d6ca5d2b9d2ec0ee4a814b2175f641a5c4299cb80b45e0f5f8356632663f89b3 - # via -r software/thunderscope/requirements.in -pyqtdarktheme-fork==2.3.2 \ - --hash=sha256:3ea94fed5df262d960378409357c63032639f749794d766f41a45ad8558b2523 \ - --hash=sha256:d96ee64f0884678fad9b6bc352d5e37d84ca786fa60ed32ffaa7e6c6bc67e964 - # via -r software/thunderscope/requirements.in -pyqtgraph==0.13.7 \ - --hash=sha256:64f84f1935c6996d0e09b1ee66fe478a7771e3ca6f3aaa05f00f6e068321d9e3 \ - --hash=sha256:7754edbefb6c367fa0dfb176e2d0610da3ada20aa7a5318516c74af5fb72bf7a - # via -r software/thunderscope/requirements.in -qtpy==2.4.2 \ - --hash=sha256:5a696b1dd7a354cb330657da1d17c20c2190c72d4888ba923f8461da67aa1a1c \ - --hash=sha256:9d6ec91a587cc1495eaebd23130f7619afa5cdd34a277acb87735b4ad7c65156 - # via pyqt-toast-notification diff --git a/src/software/thunderscope/requirements_lock.txt b/src/software/thunderscope/requirements_lock.txt index e69de29bb2..a50ed09232 100644 --- a/src/software/thunderscope/requirements_lock.txt +++ b/src/software/thunderscope/requirements_lock.txt @@ -0,0 +1,135 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# bazel run //software/thunderscope:requirements.update +# +colorama==0.4.6 \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + # via -r software/thunderscope/requirements.in +darkdetect==0.7.1 \ + --hash=sha256:3efe69f8ecd5f1b7f4fbb0d1d93f656b0e493c45cc49222380ffe2a529cbc866 \ + --hash=sha256:47be3cf5134432ddb616bbffc927237718407914993c82809983e7ccebf49013 + # via pyqtdarktheme-fork +evdev==1.7.0 \ + --hash=sha256:95bd2a1e0c6ce2cd7a2ecc6e6cd9736ff794b3ad5cb54d81d8cbc2e414d0b870 + # via -r software/thunderscope/requirements.in +netifaces==0.11.0 \ + --hash=sha256:043a79146eb2907edf439899f262b3dfe41717d34124298ed281139a8b93ca32 \ + --hash=sha256:08e3f102a59f9eaef70948340aeb6c89bd09734e0dca0f3b82720305729f63ea \ + --hash=sha256:0f6133ac02521270d9f7c490f0c8c60638ff4aec8338efeff10a1b51506abe85 \ + --hash=sha256:18917fbbdcb2d4f897153c5ddbb56b31fa6dd7c3fa9608b7e3c3a663df8206b5 \ + --hash=sha256:2479bb4bb50968089a7c045f24d120f37026d7e802ec134c4490eae994c729b5 \ + --hash=sha256:2650beee182fed66617e18474b943e72e52f10a24dc8cac1db36c41ee9c041b7 \ + --hash=sha256:28f4bf3a1361ab3ed93c5ef360c8b7d4a4ae060176a3529e72e5e4ffc4afd8b0 \ + --hash=sha256:3ecb3f37c31d5d51d2a4d935cfa81c9bc956687c6f5237021b36d6fdc2815b2c \ + --hash=sha256:469fc61034f3daf095e02f9f1bbac07927b826c76b745207287bc594884cfd05 \ + --hash=sha256:48324183af7f1bc44f5f197f3dad54a809ad1ef0c78baee2c88f16a5de02c4c9 \ + --hash=sha256:50721858c935a76b83dd0dd1ab472cad0a3ef540a1408057624604002fcfb45b \ + --hash=sha256:54ff6624eb95b8a07e79aa8817288659af174e954cca24cdb0daeeddfc03c4ff \ + --hash=sha256:5be83986100ed1fdfa78f11ccff9e4757297735ac17391b95e17e74335c2047d \ + --hash=sha256:5f9ca13babe4d845e400921973f6165a4c2f9f3379c7abfc7478160e25d196a4 \ + --hash=sha256:73ff21559675150d31deea8f1f8d7e9a9a7e4688732a94d71327082f517fc6b4 \ + --hash=sha256:7dbb71ea26d304e78ccccf6faccef71bb27ea35e259fb883cfd7fd7b4f17ecb1 \ + --hash=sha256:815eafdf8b8f2e61370afc6add6194bd5a7252ae44c667e96c4c1ecf418811e4 \ + --hash=sha256:841aa21110a20dc1621e3dd9f922c64ca64dd1eb213c47267a2c324d823f6c8f \ + --hash=sha256:84e4d2e6973eccc52778735befc01638498781ce0e39aa2044ccfd2385c03246 \ + --hash=sha256:8f7da24eab0d4184715d96208b38d373fd15c37b0dafb74756c638bd619ba150 \ + --hash=sha256:96c0fe9696398253f93482c84814f0e7290eee0bfec11563bd07d80d701280c3 \ + --hash=sha256:aab1dbfdc55086c789f0eb37affccf47b895b98d490738b81f3b2360100426be \ + --hash=sha256:c03fb2d4ef4e393f2e6ffc6376410a22a3544f164b336b3a355226653e5efd89 \ + --hash=sha256:c37a1ca83825bc6f54dddf5277e9c65dec2f1b4d0ba44b8fd42bc30c91aa6ea1 \ + --hash=sha256:c92ff9ac7c2282009fe0dcb67ee3cd17978cffbe0c8f4b471c00fe4325c9b4d4 \ + --hash=sha256:c9a3a47cd3aaeb71e93e681d9816c56406ed755b9442e981b07e3618fb71d2ac \ + --hash=sha256:cb925e1ca024d6f9b4f9b01d83215fd00fe69d095d0255ff3f64bffda74025c8 \ + --hash=sha256:d07b01c51b0b6ceb0f09fc48ec58debd99d2c8430b09e56651addeaf5de48048 \ + --hash=sha256:e76c7f351e0444721e85f975ae92718e21c1f361bda946d60a214061de1f00a1 \ + --hash=sha256:eb4813b77d5df99903af4757ce980a98c4d702bbcb81f32a0b305a1537bdf0b1 + # via -r software/thunderscope/requirements.in +numpy==1.26.4 \ + --hash=sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b \ + --hash=sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818 \ + --hash=sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20 \ + --hash=sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0 \ + --hash=sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010 \ + --hash=sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a \ + --hash=sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea \ + --hash=sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c \ + --hash=sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71 \ + --hash=sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110 \ + --hash=sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be \ + --hash=sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a \ + --hash=sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a \ + --hash=sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5 \ + --hash=sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed \ + --hash=sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd \ + --hash=sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c \ + --hash=sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e \ + --hash=sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0 \ + --hash=sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c \ + --hash=sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a \ + --hash=sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b \ + --hash=sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0 \ + --hash=sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6 \ + --hash=sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2 \ + --hash=sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a \ + --hash=sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30 \ + --hash=sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218 \ + --hash=sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5 \ + --hash=sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07 \ + --hash=sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2 \ + --hash=sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4 \ + --hash=sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764 \ + --hash=sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef \ + --hash=sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3 \ + --hash=sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f + # via + # -r software/thunderscope/requirements.in + # pyqtgraph +packaging==24.2 \ + --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ + --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f + # via qtpy +protobuf==6.31.1 \ + --hash=sha256:0414e3aa5a5f3ff423828e1e6a6e907d6c65c1d5b7e6e975793d5590bdeecc16 \ + --hash=sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447 \ + --hash=sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6 \ + --hash=sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402 \ + --hash=sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e \ + --hash=sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9 \ + --hash=sha256:8764cf4587791e7564051b35524b72844f845ad0bb011704c3736cce762d8fe9 \ + --hash=sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39 \ + --hash=sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a + # via -r software/thunderscope/requirements.in +pyqt-toast-notification==1.3.2 \ + --hash=sha256:135736ec0f16bff41104dee3c60ac318e5d55ae3378bf26892c6d08c36088ae6 \ + --hash=sha256:82688101202737736d51ab6c74a573b32266ecb7c8b0002f913407bd369737d9 + # via -r software/thunderscope/requirements.in +pyqt6-qt6==6.8.1 \ + --hash=sha256:006d786693d0511fbcf184a862edbd339c6ed1bb3bd9de363d73a19ed4b23dff \ + --hash=sha256:08065d595f1e6fc2dde9f4450eeff89082f4bad26f600a8e9b9cc5966716bfcf \ + --hash=sha256:1eb8460a1fdb38d0b2458c2974c01d471c1e59e4eb19ea63fc447aaba3ad530e \ + --hash=sha256:20843cb86bd94942d1cd99e39bf1aeabb875b241a35a8ab273e4bbbfa63776db \ + --hash=sha256:9f3790c4ce4dc576e48b8718d55fb8743057e6cbd53a6ca1dd253ffbac9b7287 \ + --hash=sha256:a8bc2ed4ee5e7c6ff4dd1c7db0b27705d151fee5dc232bbd1bf17618f937f515 \ + --hash=sha256:d6ca5d2b9d2ec0ee4a814b2175f641a5c4299cb80b45e0f5f8356632663f89b3 + # via -r software/thunderscope/requirements.in +pyqtdarktheme-fork==2.3.2 \ + --hash=sha256:3ea94fed5df262d960378409357c63032639f749794d766f41a45ad8558b2523 \ + --hash=sha256:d96ee64f0884678fad9b6bc352d5e37d84ca786fa60ed32ffaa7e6c6bc67e964 + # via -r software/thunderscope/requirements.in +pyqtgraph==0.13.7 \ + --hash=sha256:64f84f1935c6996d0e09b1ee66fe478a7771e3ca6f3aaa05f00f6e068321d9e3 \ + --hash=sha256:7754edbefb6c367fa0dfb176e2d0610da3ada20aa7a5318516c74af5fb72bf7a + # via -r software/thunderscope/requirements.in +qtawesome==1.4.0 \ + --hash=sha256:783e414d1317f3e978bf67ea8e8a1b1498bad9dbd305dec814027e3b50521be6 \ + --hash=sha256:a4d689fa071c595aa6184171ce1f0f847677cb8d2db45382c43129f1d72a3d93 + # via -r software/thunderscope/requirements.in +qtpy==2.4.2 \ + --hash=sha256:5a696b1dd7a354cb330657da1d17c20c2190c72d4888ba923f8461da67aa1a1c \ + --hash=sha256:9d6ec91a587cc1495eaebd23130f7619afa5cdd34a277acb87735b4ad7c65156 + # via + # pyqt-toast-notification + # qtawesome diff --git a/src/software/thunderscope/robot_diagnostics/BUILD b/src/software/thunderscope/robot_diagnostics/BUILD index aee3a437d1..107b65fce8 100644 --- a/src/software/thunderscope/robot_diagnostics/BUILD +++ b/src/software/thunderscope/robot_diagnostics/BUILD @@ -35,12 +35,8 @@ py_library( ":handheld_controller", "//software/thunderscope:constants", requirement("pyqtgraph"), - ] + select({ - # TODO: remove this selection when we replace evdev to - # other macos supported libs. - "@platforms//os:linux": [requirement("evdev")], - "//conditions:default": [], - }), + requirement("evdev"), + ], ) py_library( diff --git a/src/software/thunderscope/robot_diagnostics/handheld_controller.py b/src/software/thunderscope/robot_diagnostics/handheld_controller.py index d3a39a3ab0..c8e1bc2121 100644 --- a/src/software/thunderscope/robot_diagnostics/handheld_controller.py +++ b/src/software/thunderscope/robot_diagnostics/handheld_controller.py @@ -1,11 +1,6 @@ import numpy -# TODO: remove the try-catch when we rewrite this with macOS-compatible lib -try: - from evdev import InputDevice, ecodes -except ImportError: - pass - +from evdev import InputDevice, ecodes from threading import Thread diff --git a/src/software/thunderscope/robot_diagnostics/handheld_controller_widget.py b/src/software/thunderscope/robot_diagnostics/handheld_controller_widget.py index 12e9464703..ba9473509e 100644 --- a/src/software/thunderscope/robot_diagnostics/handheld_controller_widget.py +++ b/src/software/thunderscope/robot_diagnostics/handheld_controller_widget.py @@ -1,11 +1,7 @@ import numpy -# TODO: remove the try-catch when we rewrite this with macOS-compatible lib -try: - import evdev - from evdev import ecodes -except ImportError: - pass +import evdev +from evdev import ecodes from proto.import_all_protos import * from pyqtgraph.Qt.QtWidgets import * diff --git a/src/software/thunderscope/thunderscope_main.py b/src/software/thunderscope/thunderscope_main.py index 7c764a19a9..59de112b46 100644 --- a/src/software/thunderscope/thunderscope_main.py +++ b/src/software/thunderscope/thunderscope_main.py @@ -8,11 +8,6 @@ import google.protobuf from google.protobuf.internal import api_implementation -from software.thunderscope.binary_context_managers.runtime_manager import ( - runtime_manager_instance, -) - - protobuf_impl_type = api_implementation.Type() assert protobuf_impl_type == "upb", ( f"Trying to use the {protobuf_impl_type} protobuf implementation. " @@ -21,20 +16,16 @@ ) from software.thunderscope.thunderscope import Thunderscope -from software.thunderscope.constants import LogLevels from software.thunderscope.binary_context_managers import * from proto.import_all_protos import * from software.py_constants import * from software.thunderscope.robot_communication import RobotCommunication from software.thunderscope.wifi_communication_manager import WifiCommunicationManager -from software.thunderscope.constants import ( - EstopMode, - ProtoUnixIOTypes, -) +from software.thunderscope.constants import EstopMode, ProtoUnixIOTypes from software.thunderscope.estop_helpers import get_estop_config from software.thunderscope.proto_unix_io import ProtoUnixIO import software.thunderscope.thunderscope_config as config -from software.thunderscope.constants import CI_DURATION_S +from software.thunderscope.constants import CI_DURATION_S, LogLevels from software.thunderscope.util import * from software.thunderscope.binary_context_managers.full_system import FullSystem @@ -43,6 +34,7 @@ from software.thunderscope.binary_context_managers.tigers_autoref import TigersAutoref + ########################################################################### # Thunderscope Main # ########################################################################### @@ -439,14 +431,10 @@ def __ticker(tick_rate_ms: int) -> None: tick_rate_ms, tscope.proto_unix_io_map[ProtoUnixIOTypes.SIM], tscope ) - # Fetch the AI runtime/backends - runtime_config = runtime_manager_instance.fetch_runtime_config() - # Launch all binaries with Simulator( args.simulator_runtime_dir, args.debug_simulator, args.enable_realism ) as simulator, FullSystem( - path_to_binary=runtime_config.get_blue_runtime_path(), full_system_runtime_dir=args.blue_full_system_runtime_dir, debug_full_system=args.debug_blue_full_system, friendly_colour_yellow=False, @@ -455,7 +443,6 @@ def __ticker(tick_rate_ms: int) -> None: running_in_realtime=(not args.ci_mode), log_level=args.log_level, ) as blue_fs, FullSystem( - path_to_binary=runtime_config.get_yellow_runtime_path(), full_system_runtime_dir=args.yellow_full_system_runtime_dir, debug_full_system=args.debug_yellow_full_system, friendly_colour_yellow=True, diff --git a/src/software/thunderscope/util.py b/src/software/thunderscope/util.py index 2e3e420203..d5634557f9 100644 --- a/src/software/thunderscope/util.py +++ b/src/software/thunderscope/util.py @@ -1,4 +1,3 @@ -import platform from typing import Callable, NoReturn, TYPE_CHECKING if TYPE_CHECKING: @@ -196,10 +195,3 @@ def color_from_gradient( int(b_range[i] + (b_range[i + 1] - b_range[i]) * sig_val), int(a_range[i] + (a_range[i + 1] - a_range[i]) * sig_val), ) - - -def is_current_platform_macos() -> bool: - """Return True if the current process is running on macOS. - Uses platform.system(), which should reliably return 'Darwin' on macOS. - """ - return platform.system().lower() == "darwin" From 6e3cb2380ed34324dc07a6b258709541496575b1 Mon Sep 17 00:00:00 2001 From: adrianchan787 Date: Sat, 7 Feb 2026 13:55:36 -0800 Subject: [PATCH 33/34] Revert --- .github/actions/environment-setup/action.yml | 17 +- .github/release-drafter.yml | 11 ++ .github/workflows/main.yml | 10 +- .github/workflows/release.yml | 148 ++++++++++++++++++ .gitignore | 4 + docs/robot-software-architecture.md | 4 +- docs/setup-pi.md | 121 ++++++++++++++ docs/useful-robot-commands.md | 6 +- environment_setup/macos_requirements.txt | 13 ++ environment_setup/setup_software.sh | 5 + environment_setup/setup_software_mac.sh | 90 +++++++++++ environment_setup/util.sh | 84 +++++++--- src/.bazelrc | 2 +- src/MODULE.bazel | 3 +- src/MODULE.bazel.lock | 8 +- .../src/amun/simulator/simulator.cpp | 5 +- src/software/BUILD | 9 ++ src/software/ai/hl/stp/play/play.cpp | 3 +- .../stp/tactic/goalie/goalie_tactic_test.py | 8 +- .../pass_defender/pass_defender_fsm.cpp | 7 + src/software/embedded/BUILD | 5 + src/software/embedded/ansible/BUILD | 1 + .../embedded/ansible/tasks/setup_systemd.yml | 11 ++ .../embedded/hash_thunderloop_binary.sh | 2 +- .../field_tests/field_test_fixture.py | 1 + src/software/logger/BUILD | 14 +- src/software/logger/compat_flags.h | 17 ++ src/software/logger/csv_sink.cpp | 6 +- src/software/logger/log_merger.cpp | 6 +- src/software/logger/log_merger.h | 16 +- src/software/logger/logger.h | 7 +- src/software/logger/proto_logger.cpp | 4 +- .../sensor_fusion/filter/ball_filter.cpp | 15 +- .../sensor_fusion/filter/ball_filter.h | 10 +- .../simulated_tests/simulated_test_fixture.py | 2 + src/software/thunderscope/BUILD | 2 + .../binary_context_managers/BUILD | 10 ++ .../binary_context_managers/full_system.py | 29 ++-- .../game_controller.py | 9 +- .../runtime_installer.py | 67 ++++++++ .../binary_context_managers/runtime_loader.py | 139 ++++++++++++++++ .../runtime_manager.py | 49 ++++++ src/software/thunderscope/constants.py | 14 ++ src/software/thunderscope/gl/widgets/BUILD | 20 +++ .../gl/widgets/gl_gamecontroller_toolbar.py | 35 +++++ .../gl/widgets/gl_runtime_installer.py | 56 +++++++ .../gl/widgets/gl_runtime_selector.py | 100 ++++++++++++ src/software/thunderscope/requirements.in | 2 +- .../thunderscope/requirements_lock.darwin.txt | 126 +++++++++++++++ .../thunderscope/requirements_lock.txt | 2 +- .../thunderscope/robot_diagnostics/BUILD | 8 +- .../robot_diagnostics/handheld_controller.py | 7 +- .../handheld_controller_widget.py | 8 +- .../thunderscope/thunderscope_main.py | 19 ++- src/software/thunderscope/util.py | 8 + 55 files changed, 1279 insertions(+), 106 deletions(-) create mode 100644 .github/release-drafter.yml create mode 100644 .github/workflows/release.yml create mode 100644 docs/setup-pi.md create mode 100644 environment_setup/macos_requirements.txt create mode 100755 environment_setup/setup_software_mac.sh create mode 100644 src/software/logger/compat_flags.h create mode 100644 src/software/thunderscope/binary_context_managers/runtime_installer.py create mode 100644 src/software/thunderscope/binary_context_managers/runtime_loader.py create mode 100644 src/software/thunderscope/binary_context_managers/runtime_manager.py create mode 100644 src/software/thunderscope/gl/widgets/gl_runtime_installer.py create mode 100644 src/software/thunderscope/gl/widgets/gl_runtime_selector.py create mode 100644 src/software/thunderscope/requirements_lock.darwin.txt diff --git a/.github/actions/environment-setup/action.yml b/.github/actions/environment-setup/action.yml index 4cf85911fd..ff9cdc160b 100644 --- a/.github/actions/environment-setup/action.yml +++ b/.github/actions/environment-setup/action.yml @@ -1,4 +1,12 @@ name: Environment Setup + +on: + workflow_call: + inputs: + os: + required: true + type: string + runs: using: "composite" steps: @@ -10,8 +18,15 @@ runs: sudo rm -rf /usr/share/dotnet sudo rm -rf /usr/local/lib/android sudo rm -rf /opt/ghc - + - name: Run setup_software.sh shell: bash + if: runner.os == 'Linux' run: | "${GITHUB_WORKSPACE}"/environment_setup/setup_software.sh + + - name: Run setup_software_mac.sh + shell: bash + if: runner.os == 'macOS' + run: | + "${GITHUB_WORKSPACE}"/environment_setup/setup_software_mac.sh diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000000..ff313816bd --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,11 @@ +change-template: '- $TITLE @$AUTHOR (#$NUMBER)' +name-template: 'v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' +change-title-escapes: '\<*_&' +exclude-labels: + - 'skip-changelog' + +template: | + ## Changes + + $CHANGES diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ebe85c735e..5b1c94d459 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,7 +31,8 @@ jobs: -//software/ai/hl/... \ -//software/field_tests/... \ -//software/embedded/... \ - -//toolchains/cc/... + -//toolchains/cc/... \ + -//software:unix_full_system_tar_gen - name: Jetson Nano Build Test run: | @@ -60,7 +61,8 @@ jobs: -//software/ai/hl/... \ -//software/field_tests/... \ -//software/ai/navigator/... \ - -//toolchains/cc/... + -//toolchains/cc/... \ + -//software:unix_full_system_tar_gen robot-tests: name: Robot Software Tests @@ -108,8 +110,8 @@ jobs: //software:unix_full_system \ //software/simulated_tests/... \ //software/ai/hl/... \ - //software/ai/navigator/... - + //software/ai/navigator/... + - name: Upload simulated test proto logs # Ensure that simulated test logs get uploaded if: always() diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..64b7dd6151 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,148 @@ +name: Release binary + +on: + schedule: + - cron: '0 0 * * 0' # Weekly Patch (Sundays) + - cron: '0 0 1 * *' # Monthly Minor (1st of the month) + workflow_dispatch: + inputs: + version_type: + description: 'Manual Release Level' + required: true + default: 'major' + type: choice + options: + - major + - minor + - patch + release_tag: + description: 'Specify the new release tag name (e.g., v1.2.3)' + required: false + type: string + + target_commit: + description: 'Optional commit SHA/branch to release (leave empty for current HEAD)' + required: false + type: string + default: '' + +jobs: + prepare_release: + runs-on: ubuntu-latest + outputs: + tag: ${{ steps.bump_logic.outputs.tag }} + should_release: ${{ steps.check.outputs.count > 0 }} + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event.inputs.target_commit || github.sha }} + + - name: Check for recent commits + id: check + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "count=1" >> $GITHUB_OUTPUT + else + COUNT=$(git log --since="1 week ago" --oneline | wc -l) + echo "count=$COUNT" >> $GITHUB_OUTPUT + fi + + - name: Determine Version Bump + if: steps.check.outputs.count > 0 + id: bump_logic + run: | + if [ "${{ github.event.inputs.release_tag }}" = "" ]; then + BUMP="patch" + if [ "$(date +%d)" = "01" ]; then BUMP="minor"; fi + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + BUMP="${{ github.event.inputs.version_type }}" + fi + + LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v1.0.0") + # Strip the 'v' prefix + BASE_VERSION=${LATEST_TAG#v} + + IFS='.' read -r major minor patch <<< "$BASE_VERSION" + + if [ "$BUMP" = "major" ]; then + major=$((major + 1)); minor=0; patch=0 + elif [ "$BUMP" = "minor" ]; then + minor=$((minor + 1)); patch=0 + else + patch=$((patch + 1)) + fi + + NEW_TAG="v$major.$minor.$patch" + else + NEW_TAG="${{ github.event.inputs.release_tag }}" + fi + echo "tag=$NEW_TAG" >> $GITHUB_OUTPUT + echo "Using version: $NEW_TAG" + + - name: Draft Release Notes + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: release-drafter/release-drafter@v6 + with: + version: ${{ steps.bump_logic.outputs.tag }} + tag: ${{ steps.bump_logic.outputs.tag }} + + - name: Create GitHub Release + if: steps.check.outputs.count > 0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create "${{ steps.bump_logic.outputs.tag }}" \ + --title "${{ steps.bump_logic.outputs.tag }}" \ + --generate-notes \ + --draft + + upload_assets: + needs: prepare_release + if: needs.prepare_release.outputs.should_release == 'true' + strategy: + matrix: + platform: [ubuntu-x86, mac-arm64] + include: + - platform: ubuntu-x86 + runner: ubuntu-24.04 + - platform: mac-arm64 + runner: macos-latest + runs-on: ${{ matrix.runner }} + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event.inputs.target_commit || github.ref }} + + - uses: ./.github/actions/environment-setup + + - name: Build Binaries + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + cd src + TAG="${{ needs.prepare_release.outputs.tag }}" + bazel build --show_timestamps --copt=-O3 --verbose_failures \ + -- //software:unix_full_system_tar_gen + mv bazel-bin/software/unix_full_system_tar_gen.tar.gz "${{ runner.temp }}/unix_full_system_${{ needs.prepare_release.outputs.tag }}_${{ matrix.platform }}.tar.gz" + gh release upload "$TAG" "${{ runner.temp }}/unix_full_system_${{ needs.prepare_release.outputs.tag }}_${{ matrix.platform }}.tar.gz" + + publish_release: + needs: [prepare_release, upload_assets] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.target_commit || github.sha }} + - name: Undraft Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh release edit "${{ needs.prepare_release.outputs.tag }}" --draft=false diff --git a/.gitignore b/.gitignore index 989fc31f59..076a0b3477 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,7 @@ src/.clangd # external is a special directory for bazel so it should be ignored src/external + +# macOS folder info +.DS_Store + diff --git a/docs/robot-software-architecture.md b/docs/robot-software-architecture.md index 08f9ecd6bd..bacc1d1825 100644 --- a/docs/robot-software-architecture.md +++ b/docs/robot-software-architecture.md @@ -35,9 +35,7 @@ More commands available [here](useful-robot-commands.md#off-robot-commands) ## Systemd -[Systemd](https://www.freedesktop.org/wiki/Software/systemd/) allows us to have services which start as soon as we boot the robot, will automatically restart and are individually controllable. All services have the file {service}.service, which controls the configuration of that service. Currently we have a service for thunderloop, announcements and display - -To learn more about how it works, [see the RFC](https://docs.google.com/document/d/1hN3Us2Vjr8z6ihqUVp_3L7rrjKc-EZ-l2hZJc31gNOc/edit) +[Systemd](https://www.freedesktop.org/wiki/Software/systemd/) allows us to have services which start as soon as we boot the robot, will automatically restart and are individually controllable. All services have the file {service}.service, which controls the configuration of that service. Our core service brought up by systemd is thunderloop. The thunderloop.service file can be seen [here](https://github.com/UBC-Thunderbots/Software/blob/master/src/software/embedded/linux_configs/systemd/thunderloop.service). ## Redis diff --git a/docs/setup-pi.md b/docs/setup-pi.md new file mode 100644 index 0000000000..d4549e41fc --- /dev/null +++ b/docs/setup-pi.md @@ -0,0 +1,121 @@ +# Raspberry Pi Setup Guide + +## Install Raspberry Pi OS onto MicroSD card + +### Introduction + +The microSD card in a Raspberry Pi holds its Operating System and acts as its primary storage area. The Pi will not work without it. + +The Raspberry Pi 5 is compatible with many different (primarily Linux-based) operating systems. The one we use is the standard Raspberry Pi OS (also Linux-based). + +### Install OS Using Raspberry Pi Imager + +To install the Raspberry Pi OS onto a microSD card, we use the Raspberry Pi Imager program. Install it [here](https://www.raspberrypi.com/software/). + +*Note: if using the "Download for Linux" option, you will have to right click on the installed `.AppImage` file and select `Properties` → `Permissions` → `Allow Executing File as Program` (this may appear as a checkbox) + +When you open the imager, you should see something like this: + +* image + +Once installed, follow these steps to set up the Raspberry Pi: +1. Plug the microSD card into your device. + * If your device has an SD card slot, use a microSD to SD card adapter. This adapter is available in the tbots EDC space as of Jan 2026. + * If your device only has a USB slot, use a combination of a microSD to SD card adapter and USB SD card reader. Again, both of these adapters are available in the tbots EDC space. +2. Open and configure the Raspberry Pi Imager program as follows: + * Raspberry Pi Device: Raspberry Pi 5 + * Operating System: Raspberry Pi OS (64-bit) + * Storage: `` + * Customization: + * Hostname: ``.local (Ex: `aimbot`.local). The `local` part is autofilled by the Raspberry Pi Imager. + * Localization → Timezone: America/Vancouver + * User → Username: robot + * User → Password: `` (ask someone if you don't know what the correct password is) + * WiFi → Secure Network → SSID: `` (`tbots` as of Jan 2026) + * WiFi → Password: `` (ask someone if you don't know what the correct password is) + * Remote Access: Enable SSH and select "Use Password Authentication" + * Click the "Write" button and wait until complete + + Once the write process is complete, the Raspberry Pi OS is set up! You can verify the write was successful using several methods; the following is only one of these methods: + * Connect the Raspberry Pi to an HDMI output screen using a microHDMI to HDMI cable. + * You can also use an HDMI to HDMI cable with a microHDMI to HDMI adapter. We have one of these adapters in the tbots EDC space as of Jan 2026. + * The output screen on the HDMI should appear similar to a default laptop/PC screen. If this is the case, the write was successful. + * If you do not see the above, and instead see text on a black screen with messages akin to "Unable to read partition as FAT" - the write was not successful. + +## Configure Raspberry Pi with Thunderbots Service + +### Introduction + +We use the Raspberry Pi OS, which is based on the Linux kernel. The Linux kernel uses systemd as its service manager. Our core service is [thunderloop.service](https://github.com/UBC-Thunderbots/Software/blob/master/src/software/embedded/linux_configs/systemd/thunderloop.service). + +To run and manage the thunderloop service on the Pi, however, there are many dependencies (drivers, admin control, internet, etc.) that must be set up first. Luckily, all of this setup can be done using one command with an Ansible playbook. + +Read more about Ansible in our robot software architecture documentation [here](https://github.com/UBC-Thunderbots/Software/blob/master/docs/robot-software-architecture.md#ansible). + +### Configuration + +#### Internet Configuration + +Before running the Ansible command, we must do the following to configure internet access to the Pi: +1. Ensure your Raspberry Pi is connected to internet through an ethernet cable. Do this by connecting one end to the Rapsberry Pi and the other to your device (PC/laptop). Ensure your ethernet cable is not broken, as there are many non-functioning ones in the Thunderbots EDC space. +2. On your device (PC/laptop), configure network settings through the following steps: + 1. Go to network settings. You should see something like this: + + * image + + 2. Click the settings icon on the right (seen in the image above). + 3. Under the IPv4 tab, select "Shared to other computers" + + * image + + 4. Under the IPv6 tab, select "Disable" + * image + + 5. Apply changes +3. Enable IP forwarding from your device to the Pi with the following command (this has to be reconfigured every time you boot your device): +```bash +sudo sysctl -w net.ipv4.ip_forward=1 +``` +4. Enable Network Address Translation (NAT) between your Pi and device with the following commands (this has to be reconfigured every time you boot your device): +```bash +sudo iptables -t nat -A POSTROUTING -o wlo1 -j MASQUERADE +sudo iptables -A FORWARD -i eno2 -o wlo1 -j ACCEPT +sudo iptables -A FORWARD -i wlo1 -o eno2 -m state --state RELATED,ESTABLISHED -j ACCEPT +``` + +#### Thunderloop Service Configuration Through Ansible +1. SSH into the Raspberry Pi from your device by connecting to the `tbots` WiFi and typing the following in the command terminal: + * ssh `robot@` (Ex: `ssh robot@aimbot.local`) + * enter the password when prompted +2. Verify that the Pi has internet connection by pinging Google's Public DNS Service with the following command: +```bash +ping -c 3 8.8.8.8 +``` + * If successful, you should see all packets transmitted and received at some point in the return message. Ex: ```3 packets transmitted, 3 received, 0% packet loss, time 2004ms```. + * If not successful, you will see packets not received. Ex: ```3 packets transmitted, 0 received, 100% packet loss, time 2052ms```. +3. Exit the SSH connection to the Raspberry Pi with the `exit` command. +4. Change directory to `Software/src` and run the bazel ansible command: +```bash +bazel run //software/embedded/ansible:run_ansible --platforms=//toolchains/cc:robot --//software/embedded:host_platform=PI -- --playbook setup_pi.yml --hosts .local --ssh_pass +``` + * Ensure you configure `` and `` in the command above. Copy/pasting the above won't work. + * This may take a while. +5. Done! + +### Common Errors and Debugging + +**Cannot SSH into Pi** +A: Confirm that your device is connected to the `tbots` WiFi + +**Raspberry Pi shuts off (light turns red, HDMI output disconnects if connected) while the Bazel Ansible command is running** +A: This is a power brownout (voltage drops below required threshold). There are too many peripherals connected. Disconnect some and try again. + +**Raspberry Pi unresponsive and LED always solid green** +A: This is usually an indicator that the Pi's SD Card is corrupted or empty. Operational Raspberry Pi's usually have a flickering LED. Fix by reprovisioning the SD Card with the Raspberry Pi Imager (directions above). + + + + + + + diff --git a/docs/useful-robot-commands.md b/docs/useful-robot-commands.md index cf9415ab38..1ede1bcd24 100644 --- a/docs/useful-robot-commands.md +++ b/docs/useful-robot-commands.md @@ -156,7 +156,7 @@ bazel run //software/embedded/ansible:run_ansible --platforms=//toolchains/cc:ro ### Raspberry Pi ```bash -bazel run //software/embedded/ansible:run_ansible --platforms=//toolchains/cc:robot --//software/embedded:host_platform=PI -- --playbook setup_raspberry_pi.yml --hosts --ssh_pass +bazel run //software/embedded/ansible:run_ansible --platforms=//toolchains/cc:robot --//software/embedded:host_platform=PI -- --playbook setup_pi.yml --hosts --ssh_pass ``` ## Robot Diagnostics @@ -189,7 +189,7 @@ Runs the robot auto test fixture on a robot through Ansible, which tests the mot From Software/src: ```bash -bazel run //software/embedded/ansible:run_ansible --//software/embedded:host_platform= --platforms=//toolchains/cc:robot -- --playbook robot_auto_test_playbook.yml --hosts --ssh_pass +bazel run //software/embedded/ansible:run_ansible --//software/embedded:host_platform= --platforms=//toolchains/cc:robot -- --playbook robot_auto_test_playbook.yml --hosts --ssh_pass ``` * replace the \ with the target platform for the robot (either `PI` or `NANO`) @@ -202,7 +202,7 @@ bazel run //software/embedded/ansible:run_ansible --//software/embedded:host_pla ## Systemd Services -Status shows whether the service is running and some recent logs. More logs can be found using `journalctl` shown below. More control can be achieved with `systemctl`. Valid `` are `thunderloop`, `display`, and `wifi_announcements` +Status shows whether the service is running and some recent logs. More logs can be found using `journalctl` shown below. More control can be achieved with `systemctl`. Currently, the only valid `` is `thunderloop`. ```bash service status diff --git a/environment_setup/macos_requirements.txt b/environment_setup/macos_requirements.txt new file mode 100644 index 0000000000..8e4cf0bb42 --- /dev/null +++ b/environment_setup/macos_requirements.txt @@ -0,0 +1,13 @@ +ansible-lint==24.12.2 +pyqtgraph==0.13.7 +thefuzz==0.19.0 +iterfzf==0.5.0.20.0 +python-Levenshtein==0.25.1 +psutil==5.9.0 +PyOpenGL==3.1.6 +ruff==0.5.5 +pyqt-toast-notification==1.3.2 +grpcio-tools==1.71.0 +platformio==6.1.18 +pyqt6==6.9.1 + diff --git a/environment_setup/setup_software.sh b/environment_setup/setup_software.sh index f3701b7087..2a8c9a5660 100755 --- a/environment_setup/setup_software.sh +++ b/environment_setup/setup_software.sh @@ -143,6 +143,10 @@ sudo cp "$CURR_DIR/../src/software/autoref/DIV_B.txt" "/opt/tbotspython/autoRefe print_status_msg "Finished setting up AutoRef" +# setup external_runtimes +sudo mkdir /opt/tbotspython/external_runtimes +sudo chown -R $USER:$USER /opt/tbotspython/external_runtimes/ + # Install Bazel print_status_msg "Installing Bazel" @@ -160,6 +164,7 @@ print_status_msg "Done setting up cross compiler for robot software" print_status_msg "Setting Up Python Development Headers" install_python_dev_cross_compile_headers $g_arch +install_python_toolchain_headers print_status_msg "Done Setting Up Python Development Headers" print_status_msg "Setting Up PlatformIO" diff --git a/environment_setup/setup_software_mac.sh b/environment_setup/setup_software_mac.sh new file mode 100755 index 0000000000..b714235023 --- /dev/null +++ b/environment_setup/setup_software_mac.sh @@ -0,0 +1,90 @@ +#!/bin/bash +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~# +# UBC Thunderbots macOS Software Setup +# +# This script will install all required libraries and dependencies to build +# and run the Thunderbots codebase on macOS, including the AI and unit tests. +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~# + +# Save the parent dir of this script +CURR_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +cd "$CURR_DIR" || exit + +source util.sh + +# Since we only support MacOS Arm chips (M series), we can use "Darwin" as the identifier +# for mac setup procedures and ignore the architecture for now. +sys=$(uname -s) + +print_status_msg "Installing Utilities and Dependencies" + +# Update Homebrew +brew update + +# Install required packages +host_software_packages=( + cmake@4 + python@3.12 + bazelisk + openjdk@21 + pyqt@6 + qt@6 + node@20 + go@1.24 + clang-format@20 +) + +for pkg in "${host_software_packages[@]}"; do + if ! brew list "$pkg" &>/dev/null; then + print_status_msg "Installing $pkg..." + brew install "$pkg" + else + print_status_msg "$pkg already installed, skipping..." + fi +done + +# Set up cache +mkdir /tmp/tbots_download_cache + +# Set up Python +print_status_msg "Setting Up Python Environment" + +# Create virtual environment +sudo python3.12 -m venv /opt/tbotspython +chmod +source /opt/tbotspython/bin/activate + +# Install Python dependencies +sudo pip install --upgrade pip +sudo pip install -r macos_requirements.txt + +print_status_msg "Done Setting Up Python Environment" + +print_status_msg "Fetching game controller" +install_gamecontroller $sys + +print_status_msg "Setting up TIGERS AutoRef" +install_autoref $sys +sudo chmod +x "$CURR_DIR/../src/software/autoref/run_autoref.sh" +sudo cp "$CURR_DIR/../src/software/autoref/DIV_B.txt" "/opt/tbotspython/autoReferee/config/geometry/DIV_B.txt" +print_status_msg "Finished setting up AutoRef" + +print_status_msg "Setting up cross compiler for robot software" +install_cross_compiler $sys +print_status_msg "Done setting up cross compiler for robot software" + +print_status_msg "Setting Up Python Development Headers" +install_python_toolchain_headers +print_status_msg "Done Setting Up Python Development Headers" + +print_status_msg "Granting Permissions to /opt/tbotspython" +sudo chown -R $(id -u):$(id -g) /opt/tbotspython +print_status_msg "Done Granting Permissions to /opt/tbotspython" + +print_status_msg "Set up ansible-lint" +/opt/tbotspython/bin/ansible-galaxy collection install ansible.posix +print_status_msg "Finished setting up ansible-lint" + +print_status_msg "Software Setup Complete" +print_status_msg "Note: Some changes require a new terminal session to take effect" + diff --git a/environment_setup/util.sh b/environment_setup/util.sh index e3603b5510..fb85ba8a2b 100755 --- a/environment_setup/util.sh +++ b/environment_setup/util.sh @@ -1,11 +1,20 @@ install_autoref() { - autoref_commit=b30660b78728c3ce159de8ae096181a1ec52e9ba - wget -N https://github.com/TIGERs-Mannheim/AutoReferee/archive/${autoref_commit}.zip -O /tmp/tbots_download_cache/autoReferee.zip - unzip -q -o -d /tmp/tbots_download_cache/ /tmp/tbots_download_cache/autoReferee.zip + if is_darwin $1; then + autoref_version=1.5.5 + curl -L https://github.com/TIGERs-Mannheim/AutoReferee/releases/download/${autoref_version}/autoReferee.zip -o /tmp/tbots_download_cache/autoReferee.zip + unzip -q -o -d /tmp/tbots_download_cache/ /tmp/tbots_download_cache/autoReferee.zip - /tmp/tbots_download_cache/AutoReferee-${autoref_commit}/./gradlew installDist -p /tmp/tbots_download_cache/AutoReferee-${autoref_commit} -Dorg.gradle.java.home=/opt/tbotspython/bin/jdk - mv /tmp/tbots_download_cache/AutoReferee-${autoref_commit}/build/install/autoReferee /opt/tbotspython/ - rm -rf /tmp/tbots_download_cache/autoReferee.zip /tmp/tbots_download_cache/AutoReferee-${autoref_commit} + sudo mv /tmp/tbots_download_cache/autoReferee /opt/tbotspython/ + rm -rf /tmp/tbots_download_cache/autoReferee.zip + else + autoref_commit=b30660b78728c3ce159de8ae096181a1ec52e9ba + wget -N https://github.com/TIGERs-Mannheim/AutoReferee/archive/${autoref_commit}.zip -O /tmp/tbots_download_cache/autoReferee.zip + unzip -q -o -d /tmp/tbots_download_cache/ /tmp/tbots_download_cache/autoReferee.zip + + /tmp/tbots_download_cache/AutoReferee-${autoref_commit}/./gradlew installDist -p /tmp/tbots_download_cache/AutoReferee-${autoref_commit} -Dorg.gradle.java.home=/opt/tbotspython/bin/jdk + mv /tmp/tbots_download_cache/AutoReferee-${autoref_commit}/build/install/autoReferee /opt/tbotspython/ + rm -rf /tmp/tbots_download_cache/autoReferee.zip /tmp/tbots_download_cache/AutoReferee-${autoref_commit} + fi } install_bazel() { @@ -25,25 +34,47 @@ install_clang_format() { install_cross_compiler() { file_name=aarch64-tbots-linux-gnu-for-aarch64 - if is_x86 $1; then - file_name=aarch64-tbots-linux-gnu-for-x86 + if is_darwin $1; then + full_file_name=$file_name.tar.xz + curl -L "https://raw.githubusercontent.com/UBC-Thunderbots/Software-External-Dependencies/refs/heads/main/toolchain/$full_file_name" \ + -o /tmp/tbots_download_cache/$full_file_name + tar -xf /tmp/tbots_download_cache/$full_file_name -C /tmp/tbots_download_cache/ + sudo mv /tmp/tbots_download_cache/aarch64-tbots-linux-gnu /opt/tbotspython + rm /tmp/tbots_download_cache/$full_file_name + else + if is_x86 $1; then + file_name=aarch64-tbots-linux-gnu-for-x86 + fi + full_file_name=$file_name.tar.xz + wget https://raw.githubusercontent.com/UBC-Thunderbots/Software-External-Dependencies/refs/heads/main/toolchain/$full_file_name -O /tmp/tbots_download_cache/$full_file_name + tar -xf /tmp/tbots_download_cache/$full_file_name -C /tmp/tbots_download_cache/ + sudo mv /tmp/tbots_download_cache/aarch64-tbots-linux-gnu /opt/tbotspython + rm /tmp/tbots_download_cache/$full_file_name fi - full_file_name=$file_name.tar.xz - wget https://raw.githubusercontent.com/UBC-Thunderbots/Software-External-Dependencies/refs/heads/main/toolchain/$full_file_name -O /tmp/tbots_download_cache/$full_file_name - tar -xf /tmp/tbots_download_cache/$full_file_name -C /tmp/tbots_download_cache/ - sudo mv /tmp/tbots_download_cache/aarch64-tbots-linux-gnu /opt/tbotspython - rm /tmp/tbots_download_cache/$full_file_name } install_gamecontroller () { - arch=arm64 - if is_x86 $1; then - arch=amd64 - fi + if is_darwin $1; then + curl -L https://github.com/RoboCup-SSL/ssl-game-controller/archive/refs/tags/v3.17.0.zip -o /tmp/tbots_download_cache/ssl-game-controller.zip + unzip -q -o -d /tmp/tbots_download_cache/ /tmp/tbots_download_cache/ssl-game-controller.zip + cd /tmp/tbots_download_cache/ssl-game-controller-3.17.0 + make install + go build cmd/ssl-game-controller/main.go + sudo mv main /opt/tbotspython/gamecontroller + sudo chmod +x /opt/tbotspython/gamecontroller + + cd - + sudo rm -rf /tmp/tbots_download_cache/ssl-game-controller-3.17.0 /tmp/tbots_download_cache/go /tmp/tbots_download_cache/go.tar.gz /tmp/tbots_download_cache/ssl-game-controller.zip + else + arch=arm64 + if is_x86 $1; then + arch=amd64 + fi - wget https://github.com/RoboCup-SSL/ssl-game-controller/releases/download/v3.16.1/ssl-game-controller_v3.16.1_linux_${arch} -O /tmp/tbots_download_cache/gamecontroller - sudo mv /tmp/tbots_download_cache/gamecontroller /opt/tbotspython/gamecontroller - sudo chmod +x /opt/tbotspython/gamecontroller + wget https://github.com/RoboCup-SSL/ssl-game-controller/releases/download/v3.16.1/ssl-game-controller_v3.16.1_linux_${arch} -O /tmp/tbots_download_cache/gamecontroller + sudo mv /tmp/tbots_download_cache/gamecontroller /opt/tbotspython/gamecontroller + sudo chmod +x /opt/tbotspython/gamecontroller + fi } install_java () { @@ -90,6 +121,11 @@ install_python_dev_cross_compile_headers() { rm -rf /tmp/tbots_download_cache/python-3.12.0.tar.xz } +install_python_toolchain_headers() { + sudo mkdir -p /opt/tbotspython/py_headers/include/ + sudo ln -sfn "$(python3.12-config --includes | awk '{for(i=1;i<=NF;++i) if ($i ~ /^-I/) print substr($i, 3)}' | head -n1)" /opt/tbotspython/py_headers/include/ +} + is_x86() { if [[ $1 == "x86_64" ]]; then return 0 @@ -98,6 +134,14 @@ is_x86() { fi } +is_darwin() { + if [[ $1 == "Darwin" ]]; then + return 0 + else + return 1 + fi +} + print_status_msg () { echo "================================================================" echo $1 diff --git a/src/.bazelrc b/src/.bazelrc index 03d0b13b5b..28695e559f 100644 --- a/src/.bazelrc +++ b/src/.bazelrc @@ -66,7 +66,7 @@ build --incompatible_remove_legacy_whole_archive=False # Escalate Warnings to fail Compile for Thunderbots code build --features=external_include_paths -build --per_file_copt=proto/.*,proto/message_translation/.*,proto/primitive/.*,software/.*,shared/.*,-external/.*@-Wall,-Wextra,-Wno-unused-parameter,-Wno-deprecated,-Werror,-Wno-deprecated-declarations +build:linux --per_file_copt=proto/.*,proto/message_translation/.*,proto/primitive/.*,software/.*,shared/.*,-external/.*@-Wall,-Wextra,-Wno-unused-parameter,-Wno-deprecated,-Werror,-Wno-deprecated-declarations # TODO: #3492 # build --per_file_copt=software/.*,shared/.*,-external/.*@-Wconversion diff --git a/src/MODULE.bazel b/src/MODULE.bazel index 7c1597ca68..c7c7167ef2 100644 --- a/src/MODULE.bazel +++ b/src/MODULE.bazel @@ -22,6 +22,7 @@ bazel_dep(name = "rules_proto", version = "7.1.0") bazel_dep(name = "yaml-cpp", version = "0.8.0") bazel_dep(name = "buildifier_prebuilt", version = "8.0.3") bazel_dep(name = "pybind11_protobuf", version = "0.0.0-20250210-f02a2b7") +bazel_dep(name = "bazel_lib", version = "3.1.0") ############################################## # Load PIP packages and our Requirements @@ -268,7 +269,7 @@ new_local_repository( new_local_repository( name = "py_cc_toolchain_host", build_file = "@//extlibs:py_cc_toolchain.BUILD", - path = "/usr/include/python3.12/", + path = "/opt/tbotspython/py_headers/include/python3.12/", ) new_local_repository( diff --git a/src/MODULE.bazel.lock b/src/MODULE.bazel.lock index ce4bc12db9..180f9e27bf 100644 --- a/src/MODULE.bazel.lock +++ b/src/MODULE.bazel.lock @@ -41,6 +41,8 @@ "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", "https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", + "https://bcr.bazel.build/modules/bazel_lib/3.1.0/MODULE.bazel": "6809765c14e3c766a9b9286c7b0ec56ed87a73326e48fe01749f0c0fdcfe3287", + "https://bcr.bazel.build/modules/bazel_lib/3.1.0/source.json": "aaf7c2dc816219f4cb356c9d65f2555fb7f9543e537199f74a921f7877d23dfb", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", @@ -52,7 +54,8 @@ "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", - "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/source.json": "7ebaefba0b03efe59cac88ed5bbc67bcf59a3eff33af937345ede2a38b2d368a", "https://bcr.bazel.build/modules/boringssl/0.0.0-20211025-d4f1ab9/MODULE.bazel": "6ee6353f8b1a701fe2178e1d925034294971350b6d3ac37e67e5a7d463267834", "https://bcr.bazel.build/modules/boringssl/0.0.0-20230215-5c22014/MODULE.bazel": "4b03dc0d04375fa0271174badcd202ed249870c8e895b26664fd7298abea7282", "https://bcr.bazel.build/modules/boringssl/0.0.0-20240530-2db0eb3/MODULE.bazel": "d0405b762c5e87cd445b7015f2b8da5400ef9a8dbca0bfefa6c1cea79d528a97", @@ -220,7 +223,8 @@ "https://bcr.bazel.build/modules/rules_rust/0.45.1/MODULE.bazel": "a69d0db3a958fab2c6520961e1b2287afcc8b36690fd31bbc4f6f7391397150d", "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", - "https://bcr.bazel.build/modules/rules_shell/0.3.0/source.json": "c55ed591aa5009401ddf80ded9762ac32c358d2517ee7820be981e2de9756cf3", + "https://bcr.bazel.build/modules/rules_shell/0.4.1/MODULE.bazel": "00e501db01bbf4e3e1dd1595959092c2fadf2087b2852d3f553b5370f5633592", + "https://bcr.bazel.build/modules/rules_shell/0.4.1/source.json": "4757bd277fe1567763991c4425b483477bb82e35e777a56fd846eb5cceda324a", "https://bcr.bazel.build/modules/rules_swift/1.16.0/MODULE.bazel": "4a09f199545a60d09895e8281362b1ff3bb08bbde69c6fc87aff5b92fcc916ca", "https://bcr.bazel.build/modules/rules_swift/2.1.1/MODULE.bazel": "494900a80f944fc7aa61500c2073d9729dff0b764f0e89b824eb746959bc1046", "https://bcr.bazel.build/modules/rules_swift/2.1.1/source.json": "40fc69dfaac64deddbb75bd99cdac55f4427d9ca0afbe408576a65428427a186", diff --git a/src/extlibs/er_force_sim/src/amun/simulator/simulator.cpp b/src/extlibs/er_force_sim/src/amun/simulator/simulator.cpp index 2c00c5a798..1f64796560 100644 --- a/src/extlibs/er_force_sim/src/amun/simulator/simulator.cpp +++ b/src/extlibs/er_force_sim/src/amun/simulator/simulator.cpp @@ -677,12 +677,13 @@ void Simulator::handleSimulatorSetupCommand(const std::unique_ptr if (realism.has_vision_delay()) { - m_visionDelay = std::max(0l, realism.vision_delay()); + m_visionDelay = std::max(0l, realism.vision_delay()); } if (realism.has_vision_processing_time()) { - m_visionProcessingTime = std::max(0l, realism.vision_processing_time()); + m_visionProcessingTime = + std::max(0l, realism.vision_processing_time()); } if (realism.has_simulate_dribbling()) diff --git a/src/software/BUILD b/src/software/BUILD index dbb2297674..dbafc6f2e8 100644 --- a/src/software/BUILD +++ b/src/software/BUILD @@ -1,4 +1,6 @@ +load("@bazel_lib//lib:write_source_files.bzl", "write_source_files") load("@pybind11_bazel//:build_defs.bzl", "pybind_extension", "pybind_library") +load("@rules_pkg//pkg:tar.bzl", "pkg_tar") load("@thunderscope_deps//:requirements.bzl", "requirement") package(default_visibility = ["//visibility:public"]) @@ -27,6 +29,13 @@ cc_binary( ], ) +pkg_tar( + name = "unix_full_system_tar_gen", + srcs = [":unix_full_system"], + extension = "tar.gz", + mode = "0755", +) + cc_binary( name = "er_force_simulator_main", srcs = ["er_force_simulator_main.cpp"], diff --git a/src/software/ai/hl/stp/play/play.cpp b/src/software/ai/hl/stp/play/play.cpp index 2d60cde13d..bdeccf8b35 100644 --- a/src/software/ai/hl/stp/play/play.cpp +++ b/src/software/ai/hl/stp/play/play.cpp @@ -207,7 +207,8 @@ std::unique_ptr Play::get( new_primitives_to_assign->robot_primitives()) { primitives_to_run->mutable_robot_primitives()->insert( - google::protobuf::MapPair(robot_id, primitive)); + google::protobuf::MapPair( + robot_id, primitive)); } robots = remaining_robots; diff --git a/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py b/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py index c362026220..ee0640db37 100644 --- a/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py +++ b/src/software/ai/hl/stp/tactic/goalie/goalie_tactic_test.py @@ -30,15 +30,19 @@ # test ball very fast misses net (tbots_cpp.Point(0, 0), tbots_cpp.Vector(-5, 1), tbots_cpp.Point(-4.5, 0)), # test ball very fast get saved + # TODO (#3377): This test is flaky due to inconsistent goalie reach. The linked ticket may provide a permanent fix. ( tbots_cpp.Point(-2.5, 0), - tbots_cpp.Vector(-4.8, 1.1), + # TODO Revert velocity to (-4.8, 1.1) + tbots_cpp.Vector(-3.6, 0.825), tbots_cpp.Point(-4.5, 0), ), # test ball very fast with the goalie out of position saved + # TODO (#3377): This test is flaky due to inconsistent goalie reach. The linked ticket may provide a permanent fix. ( tbots_cpp.Point(-2, 0), - tbots_cpp.Vector(-5.5, 1), + # TODO Revert velocity to (-5.5,1) + tbots_cpp.Vector(-4.125, 0.75), tbots_cpp.Point(-4.5, -0.1), ), # ball slow inside friendly defense area diff --git a/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_fsm.cpp b/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_fsm.cpp index a14599c8a2..b14bb17439 100644 --- a/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_fsm.cpp +++ b/src/software/ai/hl/stp/tactic/pass_defender/pass_defender_fsm.cpp @@ -4,6 +4,7 @@ #include "software/ai/evaluation/intercept.h" #include "software/ai/hl/stp/tactic/move_primitive.h" #include "software/geom/algorithms/closest_point.h" +#include "software/geom/algorithms/contains.h" PassDefenderFSM::PassDefenderFSM( std::shared_ptr ai_config_ptr) @@ -18,6 +19,12 @@ bool PassDefenderFSM::passStarted(const Update& event) event.control_params.position_to_block_from.x() - ball_position.x(), event.control_params.position_to_block_from.y() - ball_position.y()); + // Make sure ball is within playing area + if (!contains(event.common.world_ptr->field().fieldLines(), ball_position)) + { + return false; + } + bool pass_started = event.common.world_ptr->ball().hasBallBeenKicked( ball_receiver_point_vector.orientation(), MIN_PASS_SPEED, MAX_PASS_ANGLE_DIFFERENCE); diff --git a/src/software/embedded/BUILD b/src/software/embedded/BUILD index f3efd2e985..f0fa408d47 100644 --- a/src/software/embedded/BUILD +++ b/src/software/embedded/BUILD @@ -110,6 +110,11 @@ sh_binary( srcs = ["setup_robot_software_deps.sh"], ) +filegroup( + name = "hash_thunderloop_binary", + srcs = ["hash_thunderloop_binary.sh"], +) + cc_test( name = "test_battery", srcs = ["battery_test.cpp"], diff --git a/src/software/embedded/ansible/BUILD b/src/software/embedded/ansible/BUILD index f20fb92ee5..31ec3924a0 100644 --- a/src/software/embedded/ansible/BUILD +++ b/src/software/embedded/ansible/BUILD @@ -18,6 +18,7 @@ py_binary( data = [ ":playbooks", ":tasks", + "//software/embedded:hash_thunderloop_binary.sh", "//software/embedded:setup_robot_software_deps", "//software/embedded:thunderloop_main", "//software/embedded/linux_configs/jetson_nano:jetson_nano_files", diff --git a/src/software/embedded/ansible/tasks/setup_systemd.yml b/src/software/embedded/ansible/tasks/setup_systemd.yml index 2f86fb75b5..eeaaa1d16c 100644 --- a/src/software/embedded/ansible/tasks/setup_systemd.yml +++ b/src/software/embedded/ansible/tasks/setup_systemd.yml @@ -15,6 +15,17 @@ dest: ~/thunderbots_binaries/ copy_links: true + - name: Sync Thunderloop MD5 Hash + ansible.posix.synchronize: + src: "{{ playbook_dir }}/../../hash_thunderloop_binary.sh" + dest: "~/hash_thunderloop_binary.sh" + copy_links: true + + - name: Make MD5 Hash Script Executable + ansible.builtin.file: + path: "~/hash_thunderloop_binary.sh" + mode: "0755" + - name: Compute Thunderloop MD5 Hash ansible.builtin.command: "/home/{{ ansible_user }}/hash_thunderloop_binary.sh" register: result diff --git a/src/software/embedded/hash_thunderloop_binary.sh b/src/software/embedded/hash_thunderloop_binary.sh index 8100f6cf3b..4fbb2206d8 100644 --- a/src/software/embedded/hash_thunderloop_binary.sh +++ b/src/software/embedded/hash_thunderloop_binary.sh @@ -14,7 +14,7 @@ a60a083f11289d0904c9c4bf8fa59a59 thunderloop using the awk command filters out only the first argument passed to it, in this case the MD5 hash ' -hash=$(md5sum ~/thunderbots_binaries/thunderloop | awk '{ print $1}') +hash=$(md5sum ~/thunderbots_binaries/thunderloop_main | awk '{ print $1}') run_date=$(date '+%x %H:%M') echo "$hash" > ~/thunderbots_hashes/thunderloop.hash diff --git a/src/software/field_tests/field_test_fixture.py b/src/software/field_tests/field_test_fixture.py index 829216fde2..42c0c9f6ee 100644 --- a/src/software/field_tests/field_test_fixture.py +++ b/src/software/field_tests/field_test_fixture.py @@ -357,6 +357,7 @@ def field_test_runner(): # Launch all binaries with FullSystem( + "software/unix_full_system", full_system_runtime_dir=runtime_dir, debug_full_system=debug_full_sys, friendly_colour_yellow=args.run_yellow, diff --git a/src/software/logger/BUILD b/src/software/logger/BUILD index d10935a852..6fa65a6909 100644 --- a/src/software/logger/BUILD +++ b/src/software/logger/BUILD @@ -9,6 +9,7 @@ cc_library( "log_merger.h", ], deps = [ + ":compat_flags", "@g3log", ], ) @@ -19,9 +20,13 @@ cc_library( "custom_logging_levels.h", "logger.h", ], - linkopts = ["-lstdc++fs"], + linkopts = select({ + "@platforms//os:linux": ["-lstdc++fs"], + "//conditions:default": [], + }), deps = [ ":coloured_cout_sink", + ":compat_flags", ":csv_sink", ":log_merger", ":plotjuggler_sink", @@ -103,6 +108,7 @@ cc_library( "custom_logging_levels.h", ], deps = [ + ":compat_flags", "@g3log", ], ) @@ -182,6 +188,7 @@ cc_library( "proto_logger.h", ], deps = [ + ":compat_flags", "//proto:tbots_cc_proto", "//software/multithreading:thread_safe_buffer", "@base64", @@ -189,3 +196,8 @@ cc_library( "@zlib", ], ) + +cc_library( + name = "compat_flags", + srcs = ["compat_flags.h"], +) diff --git a/src/software/logger/compat_flags.h b/src/software/logger/compat_flags.h new file mode 100644 index 0000000000..c57d8854d2 --- /dev/null +++ b/src/software/logger/compat_flags.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#if defined(__APPLE__) +using Clock = std::chrono::system_clock; +#else +using Clock = std::chrono::_V2::system_clock; +#endif + +#if __cplusplus > 201703L +#include +namespace fs = std::filesystem; +#else +#include +namespace fs = std::experimental::filesystem; +#endif diff --git a/src/software/logger/csv_sink.cpp b/src/software/logger/csv_sink.cpp index 07494a78fb..345442d681 100644 --- a/src/software/logger/csv_sink.cpp +++ b/src/software/logger/csv_sink.cpp @@ -1,10 +1,6 @@ #include "software/logger/csv_sink.h" -#if __cplusplus > 201703L -#include -#else -#include -#endif +#include "compat_flags.h" CSVSink::CSVSink(const std::string& log_directory) : log_directory(log_directory) {} diff --git a/src/software/logger/log_merger.cpp b/src/software/logger/log_merger.cpp index 8f82680cfa..4139e4fea5 100644 --- a/src/software/logger/log_merger.cpp +++ b/src/software/logger/log_merger.cpp @@ -11,8 +11,7 @@ std::list LogMerger::log(g3::LogMessage &log) { std::string msg = log.message(); - std::chrono::_V2::system_clock::time_point current_time = - std::chrono::system_clock::now(); + Clock::time_point current_time = std::chrono::system_clock::now(); // add passed time from testing current_time += passed_time; std::list messages_to_log = _getOldMessages(current_time); @@ -38,8 +37,7 @@ std::list LogMerger::log(g3::LogMessage &log) } } -std::list LogMerger::_getOldMessages( - std::chrono::_V2::system_clock::time_point current_time) +std::list LogMerger::_getOldMessages(Clock::time_point current_time) { std::list result; while (message_list.size() > 0) diff --git a/src/software/logger/log_merger.h b/src/software/logger/log_merger.h index 2cdbd9d679..00a44c02de 100644 --- a/src/software/logger/log_merger.h +++ b/src/software/logger/log_merger.h @@ -5,6 +5,8 @@ #include #include +#include "compat_flags.h" + /** * Handles merging repeated log messages into a single message @@ -41,11 +43,9 @@ class LogMerger * Looks through the message list for expired messages, removes them from the list and * map, and returns them as strings */ - std::list _getOldMessages( - std::chrono::_V2::system_clock::time_point current_time); + std::list _getOldMessages(Clock::time_point current_time); - const std::chrono::_V2::system_clock::duration LOG_MERGE_DURATION = - std::chrono::seconds(2); + const Clock::duration LOG_MERGE_DURATION = std::chrono::seconds(2); private: /** @@ -55,10 +55,9 @@ class LogMerger { g3::LogMessage log; std::string msg; - std::chrono::_V2::system_clock::time_point timestamp; + Clock::time_point timestamp; - Message(g3::LogMessage &log, std::string msg, - std::chrono::_V2::system_clock::time_point timestamp) + Message(g3::LogMessage &log, std::string msg, Clock::time_point timestamp) : log(log), msg(msg), timestamp(timestamp) { } @@ -68,8 +67,7 @@ class LogMerger repeat_map; // maps string messages to their number of repeats for fast access std::list message_list; // used to keep track of time order for messages - std::chrono::_V2::system_clock::duration - passed_time; // for testing, time passed manually + Clock::duration passed_time; // for testing, time passed manually bool enable_merging; }; diff --git a/src/software/logger/logger.h b/src/software/logger/logger.h index d71e6a6a48..df6691750e 100644 --- a/src/software/logger/logger.h +++ b/src/software/logger/logger.h @@ -3,12 +3,11 @@ #include #include -#include -#include #include #include #include +#include "compat_flags.h" #include "software/logger/coloured_cout_sink.h" #include "software/logger/csv_sink.h" #include "software/logger/custom_logging_levels.h" @@ -91,9 +90,9 @@ class LoggerSingleton // hermetic build principles // if log dir doesn't exist, create it - if (!std::experimental::filesystem::exists(runtime_dir)) + if (!fs::exists(runtime_dir)) { - std::experimental::filesystem::create_directories(runtime_dir); + fs::create_directories(runtime_dir); } auto csv_sink_handle = logWorker->addSink(std::make_unique(runtime_dir), diff --git a/src/software/logger/proto_logger.cpp b/src/software/logger/proto_logger.cpp index 2362267139..4cde56cab7 100644 --- a/src/software/logger/proto_logger.cpp +++ b/src/software/logger/proto_logger.cpp @@ -5,13 +5,13 @@ #include #include -#include #include #include #include #include #include "base64.h" +#include "compat_flags.h" #include "shared/constants.h" ProtoLogger::ProtoLogger(const std::string& log_path, @@ -31,7 +31,7 @@ ProtoLogger::ProtoLogger(const std::string& log_path, std::stringstream ss; ss << std::put_time(&tm, REPLAY_FILE_TIME_FORMAT.data()); log_folder_ = log_path_ + "/" + REPLAY_FILE_PREFIX + ss.str() + "/"; - std::experimental::filesystem::create_directories(log_folder_); + fs::create_directories(log_folder_); // Start logging in a separate thread log_thread_ = std::thread(&ProtoLogger::logProtobufs, this); diff --git a/src/software/sensor_fusion/filter/ball_filter.cpp b/src/software/sensor_fusion/filter/ball_filter.cpp index 1bee4930f4..0967a76ff8 100644 --- a/src/software/sensor_fusion/filter/ball_filter.cpp +++ b/src/software/sensor_fusion/filter/ball_filter.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include "shared/constants.h" @@ -264,17 +263,9 @@ BallFilter::LinearRegressionResults BallFilter::calculateLinearRegression( A.bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b); // How to calculate the error is from // https://eigen.tuxfamily.org/dox/group__TutorialLinearAlgebra.html - double regression_error = std::numeric_limits::max(); - - if ((A * regression_vector - b).norm() == 0 && b.norm() == 0) - { - regression_error = 0; - } - if (b.norm() != 0) - { - regression_error = - (A * regression_vector - b).norm() / (b.norm()); // norm() is L2 norm - } + // NOTE: using absolute error instead of relative because coordinates + // values should not affect error, also handles divide by 0 error + double regression_error = (A * regression_vector - b).norm(); // norm() is L2 norm // Find 2 points on the regression line that we solved for, and use this to construct // our own Line class diff --git a/src/software/sensor_fusion/filter/ball_filter.h b/src/software/sensor_fusion/filter/ball_filter.h index 678bef7ae3..cf108b2ac6 100644 --- a/src/software/sensor_fusion/filter/ball_filter.h +++ b/src/software/sensor_fusion/filter/ball_filter.h @@ -43,7 +43,8 @@ class BallFilter static constexpr double MAX_BUFFER_SIZE_VELOCITY_MAGNITUDE = 4.0; // The extra amount beyond the ball's max speed that we treat ball detections as valid static constexpr double MAX_ACCEPTABLE_BALL_SPEED_BUFFER = 2.0; - // The maximum error threshold to considering using the generated linear regression + // The maximum root mean squared error threshold to considering using the generated + // linear regression. // TODO (#2752): Investigate different values of error threshold static constexpr double LINEAR_REGRESSION_ERROR_THRESHOLD = 1000.0; @@ -85,6 +86,7 @@ class BallFilter struct LinearRegressionResults { Line regression_line; + // Regression error is root mean squared error double regression_error; }; @@ -131,7 +133,8 @@ class BallFilter /** * Given a buffer of ball detections, returns the line of best fit through - * the detection positions, and calculate the error of this regression. + * the detection positions, and calculate the root mean squared error of this + * regression. * Note: also considers vertical lines. * * @throws std::invalid_argument if ball_detections has less than 2 elements @@ -145,7 +148,8 @@ class BallFilter /** * Given a list of ball detections, use linear regression to find a line of best fit - * through the ball positions, and calculate the error of this regression. + * through the ball positions, and calculate the root mean squared error of this + * regression. * * @throws std::invalid_argument if ball_detections has less than 2 elements * diff --git a/src/software/simulated_tests/simulated_test_fixture.py b/src/software/simulated_tests/simulated_test_fixture.py index 10819a1a00..ad2d319dbb 100644 --- a/src/software/simulated_tests/simulated_test_fixture.py +++ b/src/software/simulated_tests/simulated_test_fixture.py @@ -520,12 +520,14 @@ def simulated_test_runner(): args.debug_simulator, args.enable_realism, ) as simulator, FullSystem( + "software/unix_full_system", f"{args.blue_full_system_runtime_dir}/test/{test_name}", args.debug_blue_full_system, False, should_restart_on_crash=False, running_in_realtime=args.enable_thunderscope, ) as blue_fs, FullSystem( + "software/unix_full_system", f"{args.yellow_full_system_runtime_dir}/test/{test_name}", args.debug_yellow_full_system, True, diff --git a/src/software/thunderscope/BUILD b/src/software/thunderscope/BUILD index 464997efca..2e7fddb8de 100644 --- a/src/software/thunderscope/BUILD +++ b/src/software/thunderscope/BUILD @@ -6,6 +6,7 @@ package(default_visibility = ["//visibility:public"]) compile_pip_requirements( name = "requirements", src = "requirements.in", + requirements_darwin = "requirements_lock.darwin.txt", requirements_txt = "requirements_lock.txt", ) @@ -20,6 +21,7 @@ py_binary( ":util", "//software/thunderscope/binary_context_managers:full_system", "//software/thunderscope/binary_context_managers:game_controller", + "//software/thunderscope/binary_context_managers:runtime_manager", "//software/thunderscope/binary_context_managers:simulator", ], ) diff --git a/src/software/thunderscope/binary_context_managers/BUILD b/src/software/thunderscope/binary_context_managers/BUILD index e7805dad6f..0fe462dd43 100644 --- a/src/software/thunderscope/binary_context_managers/BUILD +++ b/src/software/thunderscope/binary_context_managers/BUILD @@ -22,6 +22,7 @@ py_library( deps = [ "//proto:import_all_protos", "//software/networking:ssl_proto_communication", + "//software/thunderscope:util", "//software/thunderscope/common:thread_safe_circular_buffer", ], ) @@ -50,3 +51,12 @@ py_library( "//software/thunderscope:time_provider", ], ) + +py_library( + name = "runtime_manager", + srcs = [ + "runtime_installer.py", + "runtime_loader.py", + "runtime_manager.py", + ], +) diff --git a/src/software/thunderscope/binary_context_managers/full_system.py b/src/software/thunderscope/binary_context_managers/full_system.py index 5fc0bad397..8e8abc4e48 100644 --- a/src/software/thunderscope/binary_context_managers/full_system.py +++ b/src/software/thunderscope/binary_context_managers/full_system.py @@ -1,18 +1,20 @@ from __future__ import annotations -import os import logging -import time +import os import threading - +import time from subprocess import Popen, TimeoutExpired -from software.thunderscope.gl.layers.gl_obstacle_layer import ObstacleList -from software.thunderscope.proto_unix_io import ProtoUnixIO + +from software.py_constants import * from software.python_bindings import * + from proto.import_all_protos import * from software.py_constants import * from software.thunderscope.constants import LogLevels from software.thunderscope.binary_context_managers.util import is_cmd_running +from software.thunderscope.gl.layers.gl_obstacle_layer import ObstacleList +from software.thunderscope.proto_unix_io import ProtoUnixIO class FullSystem: @@ -20,6 +22,7 @@ class FullSystem: def __init__( self, + path_to_binary: str, full_system_runtime_dir: os.PathLike = None, debug_full_system: bool = False, friendly_colour_yellow: bool = False, @@ -30,6 +33,7 @@ def __init__( ) -> None: """Run FullSystem + :param path_to_binary: The path of the binary used for this unix full system :param full_system_runtime_dir: The directory to run the blue full_system in :param debug_full_system: Whether to run the full_system in debug mode :param friendly_color_yellow: a argument passed into the unix_full_system binary (--friendly_colour_yellow) @@ -38,6 +42,7 @@ def __init__( :param running_in_realtime: True if we are running fullsystem in realtime, else False :param log_level: Minimum g3log level that will be printed (DEBUG|INFO|WARNING|FATAL) """ + self.path_to_binary = path_to_binary self.full_system_runtime_dir = full_system_runtime_dir self.debug_full_system = debug_full_system self.friendly_colour_yellow = friendly_colour_yellow @@ -46,7 +51,6 @@ def __init__( self.should_run_under_sudo = run_sudo self.running_in_realtime = running_in_realtime self.log_level = log_level - self.thread = threading.Thread(target=self.__restart__, daemon=True) def __enter__(self) -> FullSystem: @@ -65,13 +69,12 @@ def __enter__(self) -> FullSystem: except: pass - self.full_system = ( - "software/unix_full_system --runtime_dir={} {} {} --log_level={}".format( - self.full_system_runtime_dir, - "--friendly_colour_yellow" if self.friendly_colour_yellow else "", - "--ci" if not self.running_in_realtime else "", - self.log_level.value, - ) + self.full_system = "{} --runtime_dir={} {} {} --log_level={}".format( + self.path_to_binary, + self.full_system_runtime_dir, + "--friendly_colour_yellow" if self.friendly_colour_yellow else "", + "--ci" if not self.running_in_realtime else "", + self.log_level.value, ) if self.should_run_under_sudo: diff --git a/src/software/thunderscope/binary_context_managers/game_controller.py b/src/software/thunderscope/binary_context_managers/game_controller.py index 20c8f11033..79cc8a0925 100644 --- a/src/software/thunderscope/binary_context_managers/game_controller.py +++ b/src/software/thunderscope/binary_context_managers/game_controller.py @@ -4,7 +4,6 @@ import random import logging import os -import socket import time from subprocess import Popen from typing import Any @@ -21,6 +20,7 @@ from software.thunderscope.common.thread_safe_circular_buffer import ( ThreadSafeCircularBuffer, ) +from software.thunderscope.util import is_current_platform_macos logger = logging.getLogger(__name__) import itertools @@ -288,10 +288,15 @@ def __send_referee_command(data: Referee) -> None: if autoref_proto_unix_io is not None: autoref_proto_unix_io.send_proto(Referee, data) + if is_current_platform_macos(): + loopback_iface = "en0" + else: + loopback_iface = "lo" + self.receive_referee_command = tbots_cpp.SSLRefereeProtoListener( Gamecontroller.REFEREE_IP, self.referee_port, - "lo", + loopback_iface, __send_referee_command, True, ) diff --git a/src/software/thunderscope/binary_context_managers/runtime_installer.py b/src/software/thunderscope/binary_context_managers/runtime_installer.py new file mode 100644 index 0000000000..952eb80613 --- /dev/null +++ b/src/software/thunderscope/binary_context_managers/runtime_installer.py @@ -0,0 +1,67 @@ +import requests +from pathlib import Path +import tarfile +import shutil +import platform +from software.thunderscope.constants import RuntimeManagerConstants + + +class RuntimeInstaller: + """Delegate class for handling runtime installation and remote interfacing""" + + def __init__(self): + self.runtime_install_targets = {} + + def fetch_remote_runtimes(self) -> list[str]: + """Requests a list of available runtimes from the remote. This is an expensive operation + and should only be called when necessary. + :return: A unique list of names for available runtimes + """ + releases = requests.get( + RuntimeManagerConstants.RELEASES_URL, + headers={"Accept": "application/vnd.github+json"}, + ).json() + + version_names = [] + + # Currently the only targets that are supported for each os + os_to_target = {"Darwin": "mac-arm64", "Linux": "ubuntu-x86"} + target = os_to_target[platform.system()] + + for release in releases: + version = release["tag_name"] + for asset in release.get("assets", []): + url = asset["browser_download_url"] + + if "unix_full_system" in url and target in url: + version_names.append(version) + self.runtime_install_targets[version] = url + + return version_names[: RuntimeManagerConstants.MAX_RELEASES_FETCHED] + + def install_runtime(self, version: str) -> None: + """Installs the runtime of the specified version or throws an error upon failure. + Ensures that the runtime is compatible with the current platform + :param version: Version of the runtime hosted on the remote to install + """ + url = self.runtime_install_targets[version] + + filename = Path(url).name + target_dir = Path(RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH) + tmp_dir = Path("/tmp") + tmp_path = tmp_dir / filename + extracted_binary_name = "unix_full_system" + + with requests.get(url, stream=True) as r: + r.raise_for_status() + with open(tmp_path, "wb") as f: + for chunk in r.iter_content(chunk_size=8192): + if chunk: + f.write(chunk) + + dest = target_dir / f"{extracted_binary_name}_{version}" + + # Our release assets for FullSystem are always tar.gz files + with tarfile.open(tmp_path, "r:*") as tar: + tar.extractall(tmp_dir) + shutil.move(tmp_dir / extracted_binary_name, dest) diff --git a/src/software/thunderscope/binary_context_managers/runtime_loader.py b/src/software/thunderscope/binary_context_managers/runtime_loader.py new file mode 100644 index 0000000000..49208a7b17 --- /dev/null +++ b/src/software/thunderscope/binary_context_managers/runtime_loader.py @@ -0,0 +1,139 @@ +from tomllib import TOMLDecodeError +from software.thunderscope.constants import RuntimeManagerConstants +import os +import tomllib +import logging + + +class RuntimeConfig: + """Class to store the names and get paths of the two binaries""" + + def __init__( + self, + blue_runtime: str | None = None, + yellow_runtime: str | None = None, + ) -> None: + """Create runtime config, replacing invalid runtimes with default FullSystem + :param blue_runtime: blue runtime name, None if default + :param yellow_runtime: yellow runtime name, None if default + """ + self.blue_runtime = ( + blue_runtime + if blue_runtime + and self._is_valid_runtime_path(self._get_runtime_path(blue_runtime)) + else RuntimeManagerConstants.DEFAULT_BINARY_NAME + ) + self.yellow_runtime = ( + yellow_runtime + if yellow_runtime + and self._is_valid_runtime_path(self._get_runtime_path(yellow_runtime)) + else RuntimeManagerConstants.DEFAULT_BINARY_NAME + ) + + def get_blue_runtime_path(self) -> str: + """Returns the path of the stored yellow runtime + :return: the absolute path of the binary as a string, or the relative path of our FullSystem + """ + return self._get_runtime_path(self.blue_runtime) + + def get_yellow_runtime_path(self) -> str: + """Returns the path of the stored yellow runtime + :return: the absolute path of the binary as a string, or the relative path of our FullSystem + """ + return self._get_runtime_path(self.yellow_runtime) + + def _get_runtime_path(self, selected_runtime: str) -> str: + """Gets the absolute path of a binary given its name, or the path of our default FullSystem + if the binary is not valid. + :param selected_runtime: the name of the selected runtime binary + :return: the absolute path of the binary as a string, or the relative path of our FullSystem + """ + file_path = os.path.join( + RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH, selected_runtime + ) + # Default to local FullSystem if it is selected or the selected binary isn't a valid runtime + if ( + selected_runtime == RuntimeManagerConstants.DEFAULT_BINARY_NAME + or not self._is_valid_runtime_path(file_path) + ): + return RuntimeManagerConstants.DEFAULT_BINARY_PATH + # Remove leading and trailing white space and return + return file_path.strip() + + def _is_valid_runtime_path(self, runtime_path: str) -> bool: + """Returns if the binary exists and if it is an executable. + :param runtime_path: the path to check + :return: True if it is a valid runtime + """ + return os.path.isfile(runtime_path) and os.access(runtime_path, os.X_OK) + + +class RuntimeLoader: + """Delegate class for handling local runtimes and managing runtime selection""" + + def fetch_installed_runtimes(self) -> list[str]: + """Fetches the list of installed runtimes from the local disk. + Creates the external runtimes directory in our local disk if it does not exist yet. + :return: A list of installed runtime names + """ + runtime_list = [RuntimeManagerConstants.DEFAULT_BINARY_NAME] + + if not os.path.isdir(RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH): + os.mkdir(RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH) + + # Check for all executable files in the directory, and add its name to the list + for file_name in os.listdir(RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH): + file_path = os.path.join( + RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH, file_name + ) + if os.access(file_path, os.X_OK): + runtime_list.append(file_name) + + return runtime_list + + def load_selected_runtimes(self, yellow_runtime: str, blue_runtime: str) -> None: + """Loads the yellow and blue runtimes specified by saving them in the local disk. + :param blue_runtime: name of the blue runtime to set + :param yellow_runtime: name of the yellow runtime to set + """ + # Format as TOML + selected_runtimes = ( + f'{RuntimeManagerConstants.RUNTIME_CONFIG_BLUE_KEY} = "{blue_runtime}"\n' + f'{RuntimeManagerConstants.RUNTIME_CONFIG_YELLOW_KEY} = "{yellow_runtime}"' + ) + + # create a new config file if it doesn't exist, and write in the format above to it + with open(RuntimeManagerConstants.RUNTIME_CONFIG_PATH, "w") as file: + file.write(selected_runtimes) + + def fetch_runtime_config(self) -> RuntimeConfig: + """Fetches the runtime configuration from the local disk, creating it if it doesn't exist. + :return: Returns the runtime configuration as a RuntimeConfig + """ + # Create empty config file if doesn't exist yet + os.makedirs( + os.path.dirname(RuntimeManagerConstants.RUNTIME_CONFIG_PATH), exist_ok=True + ) + open(RuntimeManagerConstants.RUNTIME_CONFIG_PATH, "a").close() + + try: + with open(RuntimeManagerConstants.RUNTIME_CONFIG_PATH, "rb") as file: + selected_runtime_dict = tomllib.load(file) + + # Get the persisted runtimes + config = RuntimeConfig( + selected_runtime_dict.get( + RuntimeManagerConstants.RUNTIME_CONFIG_BLUE_KEY + ), + selected_runtime_dict.get( + RuntimeManagerConstants.RUNTIME_CONFIG_YELLOW_KEY + ), + ) + + return config + except TOMLDecodeError: + logging.warning( + f"Failed to read TOML file at: {RuntimeManagerConstants.RUNTIME_CONFIG_PATH}" + ) + + return RuntimeConfig() diff --git a/src/software/thunderscope/binary_context_managers/runtime_manager.py b/src/software/thunderscope/binary_context_managers/runtime_manager.py new file mode 100644 index 0000000000..3316ec0372 --- /dev/null +++ b/src/software/thunderscope/binary_context_managers/runtime_manager.py @@ -0,0 +1,49 @@ +from software.thunderscope.binary_context_managers.runtime_installer import ( + RuntimeInstaller, +) +from software.thunderscope.binary_context_managers.runtime_loader import ( + RuntimeLoader, + RuntimeConfig, +) + + +class RuntimeManager: + """Class for interfacing with AI runtimes/backends (full system or external) on the disk""" + + def __init__(self): + self.runtime_installer = RuntimeInstaller() + self.runtime_loader = RuntimeLoader() + + def fetch_remote_runtimes(self) -> list[str]: + """Requests a list of available runtimes from the remote. Includes DEFAULT_BINARY_NAME by default + :return: A unique list of names for available runtimes + """ + return self.runtime_installer.fetch_remote_runtimes() + + def install_runtime(self, version: str) -> None: + """Installs the runtime of the specified version or throws an error upon failure + :param version: Version of the runtime hosted on the remote to install + """ + self.runtime_installer.install_runtime(version) + + def fetch_installed_runtimes(self) -> list[str]: + """Fetches the list of available runtimes from the local disk + :return: A list of names for available runtimes + """ + return self.runtime_loader.fetch_installed_runtimes() + + def load_selected_runtimes(self, yellow_runtime: str, blue_runtime: str) -> None: + """Loads the runtimes into the runtime loader config file on the local disk + :param blue_runtime: name of the blue runtime to load + :param yellow_runtime: name of the yellow runtime to load + """ + self.runtime_loader.load_selected_runtimes(yellow_runtime, blue_runtime) + + def fetch_runtime_config(self) -> RuntimeConfig: + """Fetches the runtime configuration from the local disk + :return: Returns the runtime configuration as a RuntimeConfig + """ + return self.runtime_loader.fetch_runtime_config() + + +runtime_manager_instance = RuntimeManager() diff --git a/src/software/thunderscope/constants.py b/src/software/thunderscope/constants.py index 12cb5b0d83..4ba2612e56 100644 --- a/src/software/thunderscope/constants.py +++ b/src/software/thunderscope/constants.py @@ -393,3 +393,17 @@ class LogLevels(StrEnum): INFO = "INFO" WARNING = "WARNING" FATAL = "FATAL" + + +class RuntimeManagerConstants: + """Constants for Runtime Manager""" + + RUNTIME_CONFIG_BLUE_KEY = "blue_runtime_name" + RUNTIME_CONFIG_YELLOW_KEY = "yellow_runtime_name" + DEFAULT_BINARY_PATH = "software/unix_full_system" + DEFAULT_BINARY_NAME = "Current Fullsystem" + EXTERNAL_RUNTIMES_PATH = "/opt/tbotspython/external_runtimes" + RUNTIME_CONFIG_PATH = f"{EXTERNAL_RUNTIMES_PATH}/runtime_config.toml" + RELEASES_URL = "https://api.github.com/repos/UBC-Thunderbots/Software/releases" + DOWNLOAD_URL = "https://github.com/UBC-Thunderbots/Software/releases/download/" + MAX_RELEASES_FETCHED = 5 diff --git a/src/software/thunderscope/gl/widgets/BUILD b/src/software/thunderscope/gl/widgets/BUILD index 7526278ced..1033dacfe6 100644 --- a/src/software/thunderscope/gl/widgets/BUILD +++ b/src/software/thunderscope/gl/widgets/BUILD @@ -2,6 +2,24 @@ load("@thunderscope_deps//:requirements.bzl", "requirement") package(default_visibility = ["//visibility:public"]) +py_library( + name = "gl_runtime_installer", + srcs = ["gl_runtime_installer.py"], + deps = [ + "//software/thunderscope/binary_context_managers:runtime_manager", + requirement("pyqtgraph"), + ], +) + +py_library( + name = "gl_runtime_selector", + srcs = ["gl_runtime_selector.py"], + deps = [ + "//software/thunderscope/binary_context_managers:runtime_manager", + requirement("pyqtgraph"), + ], +) + py_library( name = "gl_toolbar", srcs = ["gl_toolbar.py"], @@ -33,6 +51,8 @@ py_library( name = "gl_gamecontroller_toolbar", srcs = ["gl_gamecontroller_toolbar.py"], deps = [ + ":gl_runtime_installer", + ":gl_runtime_selector", ":gl_toolbar", "//proto:import_all_protos", "//software/networking:ssl_proto_communication", diff --git a/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py b/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py index 042b6622e8..d1aed796fd 100644 --- a/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py @@ -4,8 +4,12 @@ from proto.ssl_gc_common_pb2 import Team as SslTeam from typing import Callable, override import webbrowser +from software.thunderscope.gl.widgets.gl_runtime_selector import GLRuntimeSelectorDialog from software.thunderscope.gl.widgets.gl_toolbar import GLToolbar from software.thunderscope.proto_unix_io import ProtoUnixIO +from software.thunderscope.gl.widgets.gl_runtime_installer import ( + GLRuntimeInstallerDialog, +) import qtawesome as qta @@ -95,6 +99,20 @@ def __init__( display_text="Open GC", ) + self.runtime_installer_button = self.__setup_icon_button( + qta.icon("mdi6.download"), + "Opens a runtime installer modal", + self.__open_runtime_installer_dialog, + display_text="Install Runtimes", + ) + + self.runtime_selector_button = self.__setup_icon_button( + qta.icon("mdi6.server"), + "Select runtimes for each team", + self.__open_runtime_selector_dialog, + display_text="Select Runtimes", + ) + # disable the normal start button when no play is selected self.normal_start_enabled = True self.__toggle_normal_start_button() @@ -110,6 +128,9 @@ def __init__( self.__add_separator(self.layout()) self.layout().addWidget(self.gc_browser_button) self.layout().addStretch() + self.__add_separator(self.layout()) + self.layout().addWidget(self.runtime_installer_button) + self.layout().addWidget(self.runtime_selector_button) @override def refresh(self) -> None: @@ -248,3 +269,17 @@ def __send_gc_command(self, command: Command.Type, team: Team) -> None: """ command = ManualGCCommand(manual_command=Command(type=command, for_team=team)) self.proto_unix_io.send_proto(ManualGCCommand, command) + + def __open_runtime_installer_dialog(self) -> None: + """Opens the runtime installer modal, initializing if first time""" + if not hasattr(self, "runtime_installer_dialog"): + self.runtime_installer_dialog = GLRuntimeInstallerDialog( + parent=self.parent() + ) + + self.runtime_installer_dialog.show() + + def __open_runtime_selector_dialog(self) -> None: + """Initializes and opens the runtime selector dialog""" + self.runtime_selector_dialog = GLRuntimeSelectorDialog(parent=self.parent()) + self.runtime_selector_dialog.show() diff --git a/src/software/thunderscope/gl/widgets/gl_runtime_installer.py b/src/software/thunderscope/gl/widgets/gl_runtime_installer.py new file mode 100644 index 0000000000..f0facfc5f5 --- /dev/null +++ b/src/software/thunderscope/gl/widgets/gl_runtime_installer.py @@ -0,0 +1,56 @@ +from pyqtgraph.Qt.QtWidgets import ( + QDialog, + QWidget, + QPushButton, + QListWidget, + QAbstractItemView, + QVBoxLayout, +) + +from software.thunderscope.binary_context_managers.runtime_manager import ( + runtime_manager_instance, +) + + +class GLRuntimeInstallerDialog(QDialog): + """Modal that displays selectable list of runtimes to install""" + + def __init__(self, parent: QWidget): + """Initializes runtime installer modal, fetching a list of installable + runtimes and adding to a selectable list + + :param parent: the modal's parent + """ + super().__init__(parent) + + runtimes = runtime_manager_instance.fetch_remote_runtimes() + + self.setWindowTitle("Install runtimes") + self.setModal(True) + self.setMinimumWidth(400) + + install_button = QPushButton("Install") + install_button.clicked.connect(self.__install_selected_runtimes) + + runtime_select_list = QListWidget() + runtime_select_list.setSelectionMode( + QAbstractItemView.SelectionMode.MultiSelection + ) + runtime_select_list.setFixedHeight(200) + runtime_select_list.addItems(runtimes) + + self.runtime_select_list = runtime_select_list + + layout = QVBoxLayout(self) + layout.addWidget(runtime_select_list) + layout.addWidget(install_button) + + def __install_selected_runtimes(self) -> None: + """Installs all runtimes that are currently selected""" + selected_items = self.runtime_select_list.selectedItems() + selected_runtimes = [item.text() for item in selected_items] + + for runtime in selected_runtimes: + runtime_manager_instance.install_runtime(runtime) + + self.close() diff --git a/src/software/thunderscope/gl/widgets/gl_runtime_selector.py b/src/software/thunderscope/gl/widgets/gl_runtime_selector.py new file mode 100644 index 0000000000..1f84f3d835 --- /dev/null +++ b/src/software/thunderscope/gl/widgets/gl_runtime_selector.py @@ -0,0 +1,100 @@ +from pyqtgraph.Qt.QtWidgets import ( + QDialog, + QWidget, + QVBoxLayout, + QHBoxLayout, + QLabel, + QPushButton, + QComboBox, +) + +from software.thunderscope.binary_context_managers.runtime_manager import ( + runtime_manager_instance, +) + + +class GLRuntimeSelectorDialog(QDialog): + """Modal that displays the selectable list of runtimes for blue and yellow teams""" + + def __init__(self, parent: QWidget): + """Initializes the runtime selector modal, displaying the same list of installed + runtimes for both the blue and yellow teams. + + :param parent: the modal's parent + """ + super().__init__(parent) + + runtime_options = runtime_manager_instance.fetch_installed_runtimes() + runtime_config = runtime_manager_instance.fetch_runtime_config() + + # Put selected runtimes from config at start of list + blue_runtimes = [runtime_config.blue_runtime] + [ + x for x in runtime_options if x != runtime_config.blue_runtime + ] + yellow_runtimes = [runtime_config.yellow_runtime] + [ + x for x in runtime_options if x != runtime_config.yellow_runtime + ] + + self.setWindowTitle("Select Runtimes") + self.setModal(True) + self.setMinimumWidth(400) + + layout = QVBoxLayout(self) + + # Blue runtime + layout.addWidget(QLabel("Blue Runtime")) + self.blue_menu = QComboBox() + self.blue_menu.addItems(blue_runtimes) + self.blue_menu.currentTextChanged.connect(self._on_blue_changed) + self._blue_selection = self.blue_menu.currentText() + layout.addWidget(self.blue_menu) + + layout.addSpacing(10) + + # Yellow runtime + layout.addWidget(QLabel("Yellow Runtime")) + self.yellow_menu = QComboBox() + self.yellow_menu.addItems(yellow_runtimes) + self.yellow_menu.currentTextChanged.connect(self._on_yellow_changed) + self._yellow_selection = self.yellow_menu.currentText() + layout.addWidget(self.yellow_menu) + + # Restart note + layout.addSpacing(15) + restart_note = QLabel( + "Note: Restart Thunderscope for changes to take effect." + ) + layout.addWidget(restart_note) + + # Done button + layout.addSpacing(15) + button_row = QHBoxLayout() + button_row.addStretch() + + done_button = QPushButton("Done") + done_button.clicked.connect(self._on_done) + button_row.addWidget(done_button) + + layout.addLayout(button_row) + + def _on_blue_changed(self, value: str) -> None: + """Stores currently selected runtime for blue team + + :param value: the value of the selected option + """ + self._blue_selection = value + + def _on_yellow_changed(self, value: str) -> None: + """Stores currently selected runtime for yellow team + + :param value: the value of the selected option + """ + self._yellow_selection = value + + def _on_done(self) -> None: + """Commits the selected runtimes and closes the modal.""" + runtime_manager_instance.load_selected_runtimes( + self._yellow_selection, self._blue_selection + ) + + self.close() diff --git a/src/software/thunderscope/requirements.in b/src/software/thunderscope/requirements.in index 32a0054344..18c98bc70f 100644 --- a/src/software/thunderscope/requirements.in +++ b/src/software/thunderscope/requirements.in @@ -1,6 +1,6 @@ colorama==0.4.6 netifaces==0.11.0 -evdev==1.7.0 +evdev==1.7.0; sys_platform == "linux" numpy==1.26.4 protobuf==6.31.1 pyqtgraph==0.13.7 diff --git a/src/software/thunderscope/requirements_lock.darwin.txt b/src/software/thunderscope/requirements_lock.darwin.txt new file mode 100644 index 0000000000..87532ad2ba --- /dev/null +++ b/src/software/thunderscope/requirements_lock.darwin.txt @@ -0,0 +1,126 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# bazel run //software/thunderscope:requirements.update +# +colorama==0.4.6 \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + # via -r software/thunderscope/requirements.in +darkdetect==0.7.1 \ + --hash=sha256:3efe69f8ecd5f1b7f4fbb0d1d93f656b0e493c45cc49222380ffe2a529cbc866 \ + --hash=sha256:47be3cf5134432ddb616bbffc927237718407914993c82809983e7ccebf49013 + # via pyqtdarktheme-fork +netifaces==0.11.0 \ + --hash=sha256:043a79146eb2907edf439899f262b3dfe41717d34124298ed281139a8b93ca32 \ + --hash=sha256:08e3f102a59f9eaef70948340aeb6c89bd09734e0dca0f3b82720305729f63ea \ + --hash=sha256:0f6133ac02521270d9f7c490f0c8c60638ff4aec8338efeff10a1b51506abe85 \ + --hash=sha256:18917fbbdcb2d4f897153c5ddbb56b31fa6dd7c3fa9608b7e3c3a663df8206b5 \ + --hash=sha256:2479bb4bb50968089a7c045f24d120f37026d7e802ec134c4490eae994c729b5 \ + --hash=sha256:2650beee182fed66617e18474b943e72e52f10a24dc8cac1db36c41ee9c041b7 \ + --hash=sha256:28f4bf3a1361ab3ed93c5ef360c8b7d4a4ae060176a3529e72e5e4ffc4afd8b0 \ + --hash=sha256:3ecb3f37c31d5d51d2a4d935cfa81c9bc956687c6f5237021b36d6fdc2815b2c \ + --hash=sha256:469fc61034f3daf095e02f9f1bbac07927b826c76b745207287bc594884cfd05 \ + --hash=sha256:48324183af7f1bc44f5f197f3dad54a809ad1ef0c78baee2c88f16a5de02c4c9 \ + --hash=sha256:50721858c935a76b83dd0dd1ab472cad0a3ef540a1408057624604002fcfb45b \ + --hash=sha256:54ff6624eb95b8a07e79aa8817288659af174e954cca24cdb0daeeddfc03c4ff \ + --hash=sha256:5be83986100ed1fdfa78f11ccff9e4757297735ac17391b95e17e74335c2047d \ + --hash=sha256:5f9ca13babe4d845e400921973f6165a4c2f9f3379c7abfc7478160e25d196a4 \ + --hash=sha256:73ff21559675150d31deea8f1f8d7e9a9a7e4688732a94d71327082f517fc6b4 \ + --hash=sha256:7dbb71ea26d304e78ccccf6faccef71bb27ea35e259fb883cfd7fd7b4f17ecb1 \ + --hash=sha256:815eafdf8b8f2e61370afc6add6194bd5a7252ae44c667e96c4c1ecf418811e4 \ + --hash=sha256:841aa21110a20dc1621e3dd9f922c64ca64dd1eb213c47267a2c324d823f6c8f \ + --hash=sha256:84e4d2e6973eccc52778735befc01638498781ce0e39aa2044ccfd2385c03246 \ + --hash=sha256:8f7da24eab0d4184715d96208b38d373fd15c37b0dafb74756c638bd619ba150 \ + --hash=sha256:96c0fe9696398253f93482c84814f0e7290eee0bfec11563bd07d80d701280c3 \ + --hash=sha256:aab1dbfdc55086c789f0eb37affccf47b895b98d490738b81f3b2360100426be \ + --hash=sha256:c03fb2d4ef4e393f2e6ffc6376410a22a3544f164b336b3a355226653e5efd89 \ + --hash=sha256:c37a1ca83825bc6f54dddf5277e9c65dec2f1b4d0ba44b8fd42bc30c91aa6ea1 \ + --hash=sha256:c92ff9ac7c2282009fe0dcb67ee3cd17978cffbe0c8f4b471c00fe4325c9b4d4 \ + --hash=sha256:c9a3a47cd3aaeb71e93e681d9816c56406ed755b9442e981b07e3618fb71d2ac \ + --hash=sha256:cb925e1ca024d6f9b4f9b01d83215fd00fe69d095d0255ff3f64bffda74025c8 \ + --hash=sha256:d07b01c51b0b6ceb0f09fc48ec58debd99d2c8430b09e56651addeaf5de48048 \ + --hash=sha256:e76c7f351e0444721e85f975ae92718e21c1f361bda946d60a214061de1f00a1 \ + --hash=sha256:eb4813b77d5df99903af4757ce980a98c4d702bbcb81f32a0b305a1537bdf0b1 + # via -r software/thunderscope/requirements.in +numpy==1.26.4 \ + --hash=sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b \ + --hash=sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818 \ + --hash=sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20 \ + --hash=sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0 \ + --hash=sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010 \ + --hash=sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a \ + --hash=sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea \ + --hash=sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c \ + --hash=sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71 \ + --hash=sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110 \ + --hash=sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be \ + --hash=sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a \ + --hash=sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a \ + --hash=sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5 \ + --hash=sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed \ + --hash=sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd \ + --hash=sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c \ + --hash=sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e \ + --hash=sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0 \ + --hash=sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c \ + --hash=sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a \ + --hash=sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b \ + --hash=sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0 \ + --hash=sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6 \ + --hash=sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2 \ + --hash=sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a \ + --hash=sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30 \ + --hash=sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218 \ + --hash=sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5 \ + --hash=sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07 \ + --hash=sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2 \ + --hash=sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4 \ + --hash=sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764 \ + --hash=sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef \ + --hash=sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3 \ + --hash=sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f + # via + # -r software/thunderscope/requirements.in + # pyqtgraph +packaging==24.2 \ + --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ + --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f + # via qtpy +protobuf==6.31.1 \ + --hash=sha256:0414e3aa5a5f3ff423828e1e6a6e907d6c65c1d5b7e6e975793d5590bdeecc16 \ + --hash=sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447 \ + --hash=sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6 \ + --hash=sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402 \ + --hash=sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e \ + --hash=sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9 \ + --hash=sha256:8764cf4587791e7564051b35524b72844f845ad0bb011704c3736cce762d8fe9 \ + --hash=sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39 \ + --hash=sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a + # via -r software/thunderscope/requirements.in +pyqt-toast-notification==1.3.2 \ + --hash=sha256:135736ec0f16bff41104dee3c60ac318e5d55ae3378bf26892c6d08c36088ae6 \ + --hash=sha256:82688101202737736d51ab6c74a573b32266ecb7c8b0002f913407bd369737d9 + # via -r software/thunderscope/requirements.in +pyqt6-qt6==6.8.1 \ + --hash=sha256:006d786693d0511fbcf184a862edbd339c6ed1bb3bd9de363d73a19ed4b23dff \ + --hash=sha256:08065d595f1e6fc2dde9f4450eeff89082f4bad26f600a8e9b9cc5966716bfcf \ + --hash=sha256:1eb8460a1fdb38d0b2458c2974c01d471c1e59e4eb19ea63fc447aaba3ad530e \ + --hash=sha256:20843cb86bd94942d1cd99e39bf1aeabb875b241a35a8ab273e4bbbfa63776db \ + --hash=sha256:9f3790c4ce4dc576e48b8718d55fb8743057e6cbd53a6ca1dd253ffbac9b7287 \ + --hash=sha256:a8bc2ed4ee5e7c6ff4dd1c7db0b27705d151fee5dc232bbd1bf17618f937f515 \ + --hash=sha256:d6ca5d2b9d2ec0ee4a814b2175f641a5c4299cb80b45e0f5f8356632663f89b3 + # via -r software/thunderscope/requirements.in +pyqtdarktheme-fork==2.3.2 \ + --hash=sha256:3ea94fed5df262d960378409357c63032639f749794d766f41a45ad8558b2523 \ + --hash=sha256:d96ee64f0884678fad9b6bc352d5e37d84ca786fa60ed32ffaa7e6c6bc67e964 + # via -r software/thunderscope/requirements.in +pyqtgraph==0.13.7 \ + --hash=sha256:64f84f1935c6996d0e09b1ee66fe478a7771e3ca6f3aaa05f00f6e068321d9e3 \ + --hash=sha256:7754edbefb6c367fa0dfb176e2d0610da3ada20aa7a5318516c74af5fb72bf7a + # via -r software/thunderscope/requirements.in +qtpy==2.4.2 \ + --hash=sha256:5a696b1dd7a354cb330657da1d17c20c2190c72d4888ba923f8461da67aa1a1c \ + --hash=sha256:9d6ec91a587cc1495eaebd23130f7619afa5cdd34a277acb87735b4ad7c65156 + # via pyqt-toast-notification diff --git a/src/software/thunderscope/requirements_lock.txt b/src/software/thunderscope/requirements_lock.txt index a50ed09232..1b4d0632ee 100644 --- a/src/software/thunderscope/requirements_lock.txt +++ b/src/software/thunderscope/requirements_lock.txt @@ -12,7 +12,7 @@ darkdetect==0.7.1 \ --hash=sha256:3efe69f8ecd5f1b7f4fbb0d1d93f656b0e493c45cc49222380ffe2a529cbc866 \ --hash=sha256:47be3cf5134432ddb616bbffc927237718407914993c82809983e7ccebf49013 # via pyqtdarktheme-fork -evdev==1.7.0 \ +evdev==1.7.0 ; sys_platform == "linux" \ --hash=sha256:95bd2a1e0c6ce2cd7a2ecc6e6cd9736ff794b3ad5cb54d81d8cbc2e414d0b870 # via -r software/thunderscope/requirements.in netifaces==0.11.0 \ diff --git a/src/software/thunderscope/robot_diagnostics/BUILD b/src/software/thunderscope/robot_diagnostics/BUILD index 107b65fce8..aee3a437d1 100644 --- a/src/software/thunderscope/robot_diagnostics/BUILD +++ b/src/software/thunderscope/robot_diagnostics/BUILD @@ -35,8 +35,12 @@ py_library( ":handheld_controller", "//software/thunderscope:constants", requirement("pyqtgraph"), - requirement("evdev"), - ], + ] + select({ + # TODO: remove this selection when we replace evdev to + # other macos supported libs. + "@platforms//os:linux": [requirement("evdev")], + "//conditions:default": [], + }), ) py_library( diff --git a/src/software/thunderscope/robot_diagnostics/handheld_controller.py b/src/software/thunderscope/robot_diagnostics/handheld_controller.py index c8e1bc2121..d3a39a3ab0 100644 --- a/src/software/thunderscope/robot_diagnostics/handheld_controller.py +++ b/src/software/thunderscope/robot_diagnostics/handheld_controller.py @@ -1,6 +1,11 @@ import numpy -from evdev import InputDevice, ecodes +# TODO: remove the try-catch when we rewrite this with macOS-compatible lib +try: + from evdev import InputDevice, ecodes +except ImportError: + pass + from threading import Thread diff --git a/src/software/thunderscope/robot_diagnostics/handheld_controller_widget.py b/src/software/thunderscope/robot_diagnostics/handheld_controller_widget.py index ba9473509e..12e9464703 100644 --- a/src/software/thunderscope/robot_diagnostics/handheld_controller_widget.py +++ b/src/software/thunderscope/robot_diagnostics/handheld_controller_widget.py @@ -1,7 +1,11 @@ import numpy -import evdev -from evdev import ecodes +# TODO: remove the try-catch when we rewrite this with macOS-compatible lib +try: + import evdev + from evdev import ecodes +except ImportError: + pass from proto.import_all_protos import * from pyqtgraph.Qt.QtWidgets import * diff --git a/src/software/thunderscope/thunderscope_main.py b/src/software/thunderscope/thunderscope_main.py index 59de112b46..7c764a19a9 100644 --- a/src/software/thunderscope/thunderscope_main.py +++ b/src/software/thunderscope/thunderscope_main.py @@ -8,6 +8,11 @@ import google.protobuf from google.protobuf.internal import api_implementation +from software.thunderscope.binary_context_managers.runtime_manager import ( + runtime_manager_instance, +) + + protobuf_impl_type = api_implementation.Type() assert protobuf_impl_type == "upb", ( f"Trying to use the {protobuf_impl_type} protobuf implementation. " @@ -16,16 +21,20 @@ ) from software.thunderscope.thunderscope import Thunderscope +from software.thunderscope.constants import LogLevels from software.thunderscope.binary_context_managers import * from proto.import_all_protos import * from software.py_constants import * from software.thunderscope.robot_communication import RobotCommunication from software.thunderscope.wifi_communication_manager import WifiCommunicationManager -from software.thunderscope.constants import EstopMode, ProtoUnixIOTypes +from software.thunderscope.constants import ( + EstopMode, + ProtoUnixIOTypes, +) from software.thunderscope.estop_helpers import get_estop_config from software.thunderscope.proto_unix_io import ProtoUnixIO import software.thunderscope.thunderscope_config as config -from software.thunderscope.constants import CI_DURATION_S, LogLevels +from software.thunderscope.constants import CI_DURATION_S from software.thunderscope.util import * from software.thunderscope.binary_context_managers.full_system import FullSystem @@ -34,7 +43,6 @@ from software.thunderscope.binary_context_managers.tigers_autoref import TigersAutoref - ########################################################################### # Thunderscope Main # ########################################################################### @@ -431,10 +439,14 @@ def __ticker(tick_rate_ms: int) -> None: tick_rate_ms, tscope.proto_unix_io_map[ProtoUnixIOTypes.SIM], tscope ) + # Fetch the AI runtime/backends + runtime_config = runtime_manager_instance.fetch_runtime_config() + # Launch all binaries with Simulator( args.simulator_runtime_dir, args.debug_simulator, args.enable_realism ) as simulator, FullSystem( + path_to_binary=runtime_config.get_blue_runtime_path(), full_system_runtime_dir=args.blue_full_system_runtime_dir, debug_full_system=args.debug_blue_full_system, friendly_colour_yellow=False, @@ -443,6 +455,7 @@ def __ticker(tick_rate_ms: int) -> None: running_in_realtime=(not args.ci_mode), log_level=args.log_level, ) as blue_fs, FullSystem( + path_to_binary=runtime_config.get_yellow_runtime_path(), full_system_runtime_dir=args.yellow_full_system_runtime_dir, debug_full_system=args.debug_yellow_full_system, friendly_colour_yellow=True, diff --git a/src/software/thunderscope/util.py b/src/software/thunderscope/util.py index d5634557f9..2e3e420203 100644 --- a/src/software/thunderscope/util.py +++ b/src/software/thunderscope/util.py @@ -1,3 +1,4 @@ +import platform from typing import Callable, NoReturn, TYPE_CHECKING if TYPE_CHECKING: @@ -195,3 +196,10 @@ def color_from_gradient( int(b_range[i] + (b_range[i + 1] - b_range[i]) * sig_val), int(a_range[i] + (a_range[i + 1] - a_range[i]) * sig_val), ) + + +def is_current_platform_macos() -> bool: + """Return True if the current process is running on macOS. + Uses platform.system(), which should reliably return 'Darwin' on macOS. + """ + return platform.system().lower() == "darwin" From 212b4dbd6d9dc8ebc755514b638d890e0016089b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 7 Feb 2026 22:04:30 +0000 Subject: [PATCH 34/34] [pre-commit.ci lite] apply automatic fixes --- docs/getting-started.md | 100 ++++++++-------------------------- docs/useful-robot-commands.md | 26 --------- 2 files changed, 24 insertions(+), 102 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index a55602907c..e289356dde 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -5,35 +5,30 @@ - [Table of Contents](#table-of-contents) -- [Software Setup](#software-setup) - - [Introduction](#introduction) - - [Installation and Setup](#installation-and-setup) - - [Operating systems](#operating-systems) - - [Getting the Code](#getting-the-code) - - [Installing Software Dependencies](#installing-software-dependencies) - - [Installing an IDE](#installing-an-ide) - - [Installing an IDE: CLion](#installing-an-ide-clion) - - [Getting your Student License](#getting-your-student-license) - - [Installing CLion](#installing-clion) - - [Installing an IDE: VS Code](#installing-an-ide-vs-code) - - [Editing with Vim or NeoVim](#editing-with-vim-or-neovim) - - [Building and Running the Code](#building-and-running-the-code) - - [Building from the command line](#building-from-the-command-line) - - [Building from the command line using the fuzzy finder](#building-from-the-command-line-using-the-fuzzy-finder) - - [Building with CLion](#building-with-clion) - - [Building with VS Code](#building-with-vs-code) - - [Running our AI, Simulator, SimulatedTests or Robot Diagnostics](#running-our-ai-simulator-simulatedtests-or-robot-diagnostics) - - [Debugging](#debugging) - - [Debugging with CLion](#debugging-with-clion) - - [Debugging from the Command Line](#debugging-from-the-command-line) - - [Profiling](#profiling) - - [Callgrind](#callgrind) - - [Tracy](#tracy) - - [Building for the robot](#building-for-the-robot) - - [Deploying Robot Software to the robot](#deploying-robot-software-to-the-robot) - - [Testing Robot Software locally](#testing-robot-software-locally) - - [Setting up Virtual Robocup 2021](#setting-up-virtual-robocup-2021) - - [Setting up the SSL Simulation Environment](#setting-up-the-ssl-simulation-environment) + - [Installing Software Dependencies](#installing-software-dependencies) + - [Installing an IDE](#installing-an-ide) + - [Installing an IDE: CLion](#installing-an-ide-clion) + - [Getting your Student License](#getting-your-student-license) + - [Installing CLion](#installing-clion) + - [Installing an IDE: VS Code](#installing-an-ide-vs-code) + - [Editing with Vim or NeoVim](#editing-with-vim-or-neovim) +- [Building and Running the Code](#building-and-running-the-code) + - [Building from the command line](#building-from-the-command-line) + - [Building from the command line using the fuzzy finder](#building-from-the-command-line-using-the-fuzzy-finder) + - [Building with CLion](#building-with-clion) + - [Building with VS Code](#building-with-vs-code) + - [Running our AI, Simulator, SimulatedTests or Robot Diagnostics](#running-our-ai-simulator-simulatedtests-or-robot-diagnostics) +- [Debugging](#debugging) + - [Debugging with CLion](#debugging-with-clion) + - [Debugging from the Command Line](#debugging-from-the-command-line) +- [Profiling](#profiling) + - [Callgrind](#callgrind) + - [Tracy](#tracy) +- [Building for the robot](#building-for-the-robot) +- [Deploying Robot Software to the robot](#deploying-robot-software-to-the-robot) +- [Testing Robot Software locally](#testing-robot-software-locally) +- [Setting up Virtual Robocup 2021](#setting-up-virtual-robocup-2021) + - [Setting up the SSL Simulation Environment](#setting-up-the-ssl-simulation-environment) - [Workflow](#workflow) - [Issue and Project Tracking](#issue-and-project-tracking) - [Issues](#issues) @@ -49,53 +44,6 @@ - [Testing](#testing) - -# Software Setup - -## Introduction - -These instructions assume that you have the following accounts setup: -- [GitHub](https://github.com/login) -- [Discord](https://discord.com). Please contact a Thunderbots lead to receive the invite link. - -These instructions assume you have a basic understanding of Linux and the command line. There are many great tutorials online, such as [LinuxCommand](http://linuxcommand.org/). The most important things you'll need to know are how to move around the filesystem and how to run programs or scripts. - -## Installation and Setup - -### Operating systems - -We currently only support Linux, specifically Ubuntu. - -If you have a X86_64 machine, we support Ubuntu 22.04 LTS and Ubuntu 24.04 LTS. - -If you have a ARM64 (also known as AARCH64) machine, we support Ubuntu 24.04 LTS. - -You are welcome to use a different version or distribution of Linux, but may need to make some tweaks in order for things to work. - -You can use Ubuntu 22.04 LTS or Ubuntu 24.04 LTS inside Windows through Windows Subsystem for Linux, by following [this guide](./getting-started-wsl.md). **Running and developing Thunderbots on Windows is experimental and not officially supported.** - -### Getting the Code - -1. Open a new terminal -2. Install git by running `sudo apt-get install git` -3. Go to the [software repository](https://github.com/UBC-Thunderbots/Software) -4. Click the `Fork` button in the top-right to fork the repository ([click here to learn about Forks](https://help.github.com/en/articles/fork-a-repo)) - 1. Click on your user when prompted - 2. You should be automatically redirected to your new fork -5. Clone your fork of the repository. As GitHub is forcing users to stop using usernames and passwords for authorization, we will be using the SSH link. - - To clone using SSH: - - 1. If not setup prior, you will need to add an SSH key to your GitHub account. Instructions can be found [here](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent). For each computer you contribute to GitHub with, you will need an additional SSH Key pair linked to your account. - 2. After you have successfully set up a SSH key for your device and added it to GitHub, you can clone the repository using the following command: - 1. e.g. `git clone git@github.com:/Software.git` - 2. You can find this link under the green `Code` button on the main page of your fork on GitHub, under the SSH tab. (This should now be available after adding your SSH key to GitHub successfully.) - - Alternatively, you can clone using HTTPS. You'll need to either use a credential helper (Git Credential Manager, GitHub CLI, etc.) or a personal access token ([details here](https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls)). -6. Set up your git remotes ([what is a remote and how does it work?](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes)) - 1. You should have a remote named `origin` that points to your fork of the repository. Git will have set this up automatically when you cloned your fork in the previous step. - 2. You will need to add a second remote, named `upstream`, that points to our main Software repository, which is where you created your fork from. (**Note:** This is _not_ your fork) - 1. Open a terminal and navigate to the folder you cloned (your fork): `cd path/to/the/repository/Software` 2. Navigate to our main Software repository in your browser and copy the url from the green `Code` button. Copy the SSH url if you originally cloned with SSH, or use the HTTPS url if you previously cloned with HTTPS 3. From your terminal, add the new remote by running `git remote add upstream ` (without the angle brackets) 1. e.g. `git remote add upstream git@github.com:UBC-Thunderbots/Software.git` diff --git a/docs/useful-robot-commands.md b/docs/useful-robot-commands.md index 1ede1bcd24..b5096017c1 100644 --- a/docs/useful-robot-commands.md +++ b/docs/useful-robot-commands.md @@ -5,10 +5,6 @@ - [Table of Contents](#table-of-contents) -- [Common Debugging Steps](#common-debugging-steps) -- [Off Robot Commands](#off-robot-commands) - - [Wifi Disclaimer](#wifi-disclaimer) - - [Miscellaneous Ansible Tasks & Options](#miscellaneous-ansible-tasks--options) - [Flashing the robot's compute module](#flashing-the-robots-compute-module) - [Flashing the powerboard](#flashing-the-powerboard) - [Setting up the embedded host](#setting-up-the-embedded-host) @@ -24,28 +20,6 @@ - [Redis](#redis) - -# Common Debugging Steps -```mermaid ---- -title: Robot Debugging Steps ---- -flowchart TD - ssh("Can you SSH into the robot? - `ssh robot@192.168.0.20RobotID` (for Nanos) OR `ssh robot@192.168.5.20RobotID` (for Pis) OR `ssh robot@robot_name.local` - e.g. `ssh robot@192.168.0.203` (for Nanos) or `ssh robot@192.168.5.203` (for Pis) or `ssh robot@robert.local` - for a robot called robert with robot id 3") - ssh ---> |Yes| tloop_status - ssh --> |No - Second Try| monitor("Connect Jetson or Pi to an external monitor and check wifi connection or SSH using an ethernet cable") - ssh --> |No - First Try| restart(Restart robot) - restart --> ssh - - diagnostics("`Run Diagnostics while connected to '**tbots**' wifi`") --> robot_view - robot_view(Robot is shown as connected in 'Robot View' widget?) --> |Yes| check_motors(All motors move?) - style diagnostics stroke:#f66,stroke-width:2px,stroke-dasharray: 5 5 - - check_motors -->|Yes| field_test(Running AI?) - field_test -->|No| done(Done) style done stroke:#30fa02,stroke-width:2px,stroke-dasharray: 5 5 field_test -->|Yes| field_test_moves(Does robot move during field test?) field_test_moves --> |No| check_shell("`Check that the correct shell is placed on the robot`")