From 3b165416baa72e8084fd1250d1ee1461966cfda5 Mon Sep 17 00:00:00 2001 From: Clemens Wulff Date: Fri, 13 Mar 2026 21:11:15 +0100 Subject: [PATCH 01/13] team_com disallowed robots to send messages if not intended, and reduces the rate if we get close to the budget end --- .../bitbots_team_communication.py | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py b/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py index d52c6e7a1..1eda840f3 100755 --- a/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py +++ b/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py @@ -52,6 +52,7 @@ def __init__(self): self.rate: int = self.node.get_parameter("rate").value self.lifetime: int = self.node.get_parameter("lifetime").value self.avg_walking_speed: float = self.node.get_parameter("avg_walking_speed").value + self.rate_is_reduced: bool = False self.topics = get_parameter_dict(self.node, "topics") self.map_frame: str = self.node.get_parameter("map_frame").value @@ -66,7 +67,7 @@ def __init__(self): self.run_spin_in_thread() self.try_to_establish_connection() - self.node.create_timer(1 / self.rate, self.send_message, callback_group=MutuallyExclusiveCallbackGroup()) + self.timer = self.node.create_timer(1 / self.rate, self.send_message, callback_group=MutuallyExclusiveCallbackGroup()) self.receive_forever() def spin(self): @@ -263,6 +264,11 @@ def handle_message(self, string_message: bytes): self.team_data_publisher.publish(team_data) def send_message(self): + + if not self.rate_is_reduced: + if self.gamestate is not None and self.gamestate.secs_remaining > 180 and (self.gamestate.message_budget / self.gamestate.secs_remaining) < 11.2: + self.reduce_rate() + if not self.is_robot_allowed_to_send_message(): self.logger.debug("Robot is not allowed to send message") return @@ -301,7 +307,17 @@ def should_message_be_discarded(self, message: Proto.Message) -> bool: return is_own_message or is_message_from_oposite_team def is_robot_allowed_to_send_message(self) -> bool: - return self.gamestate is not None and not self.gamestate.penalized + #a penalized robot doesn't need to publish + if self.gamestate is not None and not self.gamestate.penalized: + return False + #if we are close to our message budget, we dont want to continue publishing + if self.gamestate is not None and (self.gamestate.message_budget > 40): + return False + #we dont want to publish messages if only one robot is in a team. (this may never occure since this is the max team size, not the number of active players) + if self.gamestate is not None and self.gamestate.players_per_team == 1: + return False + + return True def get_current_time(self) -> Time: return self.node.get_clock().now() @@ -313,6 +329,13 @@ def extract_orientation_yaw_angle(self, quaternion: Quaternion): def convert_to_euler(self, quaternion: Quaternion): return transforms3d.euler.quat2euler([quaternion.w, quaternion.x, quaternion.y, quaternion.z]) + + def reduce_rate(self): + self.rate = 1 + self.timer.cancel() + self.timer = self.node.create_timer(1 / self.rate, self.send_message, callback_group=MutuallyExclusiveCallbackGroup()) + self.rate_is_reduced = True + def main(): From 614b005b87ac58d6846198a180820229aa1b07ff Mon Sep 17 00:00:00 2001 From: Clemens Wulff Date: Sun, 12 Apr 2026 18:11:16 +0200 Subject: [PATCH 02/13] zwischenstand behavior integration --- .../behavior_dsd/main.dsd | 60 ++++++++++++++++--- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd index 60f838420..f6786f744 100644 --- a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd +++ b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd @@ -108,6 +108,22 @@ $DoOnce SECOND --> @ChangeAction + action:positioning, @LookAtFieldFeatures, @AvoidBallActive, @GoToDefensePosition + mode:freekick_second THIRD --> @ChangeAction + action:positioning, @LookAtFieldFeatures, @AvoidBallActive, @GoToDefensePosition +#PlacingWithoutTeamCom +$DoOnce + NOT_DONE --> @ForgetBall, @LookAtFieldFeatures + DONE --> $ConfigRole + GOALIE --> @ChangeAction + action:positioning, @LookAtFieldFeatures, @GoToBlockPosition + ELSE --> $BallSeen + NO --> $SecondaryStateTeamDecider + OUR --> #SearchBall + OTHER --> @AvoidBallActive, @LookAtFieldFeatures, @WalkInPlace + duration:2, @GoToRelativePosition + x:1 + y:0 + t:0, @Stand + //TODO + YES --> $CountUnpenalizedRobots + YES --> $RankToBallNoGoalieWithoutTeamCom + FIRST --> @ChangeAction + action:positioning, @LookAtFieldFeatures, @AvoidBallActive, @GoToDefensePosition + mode:freekick_first + SECOND --> @ChangeAction + action:positioning, @LookAtFieldFeatures, @AvoidBallActive, @GoToDefensePosition + mode:freekick_second + THIRD --> @ChangeAction + action:positioning, @LookAtFieldFeatures, @AvoidBallActive, @GoToDefensePosition + #Init @Stand + duration:0.1 + r:false, @ChangeAction + action:waiting, @LookForward, @Stand @@ -130,15 +146,43 @@ $BallSeen SECOND --> #SupporterRole THIRD --> #DefensePositioning +#NormalBehaviorWithoutTeamCom +$BallSeen + NO --> $ConfigRole + GOALIE --> #RolePositionWithPause + ELSE --> #SearchBall + YES --> $KickOffTimeUp + NO_NORMAL --> #StandAndLook + NO_FREEKICK --> #PlacingWithoutTeamCom + YES --> $ConfigRole + GOALIE --> #GoalieBehavior + ELSE --> $CountActiveRobotsWithoutGoalie + ONE --> $RankToBallNoGoalie + FIRST --> #StrikerRole + SECOND --> #DefensePositioning + ELSE --> $RankToBallNoGoalie + FIRST --> #StrikerRole + SECOND --> #SupporterRole + THIRD --> #DefensePositioning + #PlayingBehavior -$SecondaryStateDecider - PENALTYSHOOT --> #PenaltyShootoutBehavior - TIMEOUT --> #StandAndLook - ELSE --> $SecondaryStateTeamDecider - OUR --> #NormalBehavior - OTHER --> #Placing - NORMAL --> #NormalBehavior - OVERTIME --> #NormalBehavior +$TeamComLimitReached + YES --> $SecondaryStateDecider + PENALTYSHOOT --> #PenaltyShootoutBehavior + TIMEOUT --> #StandAndLook + ELSE --> $SecondaryStateTeamDecider + OUR --> #NormalBehaviorWithoutTeamCom + OTHER --> #PlacingWithoutTeamCom + NORMAL --> #NormalBehaviorWithoutTeamCom + OVERTIME --> #NormalBehaviorWithoutTeamCom + NO --> $SecondaryStateDecider + PENALTYSHOOT --> #PenaltyShootoutBehavior + TIMEOUT --> #StandAndLook + ELSE --> $SecondaryStateTeamDecider + OUR --> #NormalBehavior + OTHER --> #Placing + NORMAL --> #NormalBehavior + OVERTIME --> #NormalBehavior -->BodyBehavior $IsPenalized From ccdafa8677bb78de812983eebc2876eb8faf39fe Mon Sep 17 00:00:00 2001 From: Clemens Wulff Date: Sun, 12 Apr 2026 19:43:43 +0200 Subject: [PATCH 03/13] zwischenstand 2.0 --- .../capsules/game_status_capsule.py | 6 ++ .../decisions/number_penalized_players.py | 66 +++++++++++++++++++ .../behavior_dsd/main.dsd | 7 +- 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/number_penalized_players.py diff --git a/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/game_status_capsule.py b/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/game_status_capsule.py index 1a2235e9f..2dcb5a048 100644 --- a/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/game_status_capsule.py +++ b/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/game_status_capsule.py @@ -84,6 +84,12 @@ def get_seconds_since_unpenalized(self) -> float: def get_is_penalized(self) -> bool: return self.gamestate.penalized + + def get_penalized_team_mates(self) -> int: + return self.gamestate.team_mates_with_penalty + + def get_penalized_rivals(self) -> int: + return self.gamestate.rivals_with_penalty def received_gamestate(self) -> bool: return self.last_update != 0.0 diff --git a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/number_penalized_players.py b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/number_penalized_players.py new file mode 100644 index 000000000..e6b04cba9 --- /dev/null +++ b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/number_penalized_players.py @@ -0,0 +1,66 @@ +from bitbots_blackboard.body_blackboard import BodyBlackboard +from dynamic_stack_decider.abstract_decision_element import AbstractDecisionElement +from game_controller_hsl_interfaces.msg import GameState + + +class NumberPenalizedTeamMates(AbstractDecisionElement): + blackboard: BodyBlackboard + + def __init__(self, blackboard, dsd, parameters): + super().__init__(blackboard, dsd, parameters) + + def perform(self, reevaluate=False): + """ + Return number of penalized team mates + :param reevaluate: + :return: + """ + game_state_penalized_team_mates = self.blackboard.gamestate.get_penalized_team_mates() + + if game_state_penalized_team_mates == 4: + return "FOUR" + elif game_state_penalized_team_mates == 3: + return "THREE" + elif game_state_penalized_team_mates == 2: + return "TWO" + elif game_state_penalized_team_mates == 1: + return "ONE" + else: + return "ZERO" + + def get_reevaluate(self): + """ + Game state can change during the game + """ + return True + +class NumberPenalizedRivals(AbstractDecisionElement): + blackboard: BodyBlackboard + + def __init__(self, blackboard, dsd, parameters): + super().__init__(blackboard, dsd, parameters) + + def perform(self, reevaluate=False): + """ + Return number of penalized rivals + :param reevaluate: + :return: + """ + game_state_penalized_rivals = self.blackboard.gamestate.get_penalized_rivals() + + if game_state_penalized_rivals == 4: + return "FOUR" + elif game_state_penalized_rivals == 3: + return "THREE" + elif game_state_penalized_rivals == 2: + return "TWO" + elif game_state_penalized_rivals == 1: + return "ONE" + else: + return "ZERO" + + def get_reevaluate(self): + """ + Game state can change during the game + """ + return True diff --git a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd index f6786f744..789a14102 100644 --- a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd +++ b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd @@ -118,7 +118,12 @@ $DoOnce OUR --> #SearchBall OTHER --> @AvoidBallActive, @LookAtFieldFeatures, @WalkInPlace + duration:2, @GoToRelativePosition + x:1 + y:0 + t:0, @Stand //TODO - YES --> $CountUnpenalizedRobots + YES --> $NumberPenalizedTeamMates + ZERO + ONE + TWO + THREE + FOUR YES --> $RankToBallNoGoalieWithoutTeamCom FIRST --> @ChangeAction + action:positioning, @LookAtFieldFeatures, @AvoidBallActive, @GoToDefensePosition + mode:freekick_first SECOND --> @ChangeAction + action:positioning, @LookAtFieldFeatures, @AvoidBallActive, @GoToDefensePosition + mode:freekick_second From db26bedf623042c35fad493d9aaf79b33d0ea920 Mon Sep 17 00:00:00 2001 From: Clemens Wulff Date: Tue, 12 May 2026 14:41:47 +0200 Subject: [PATCH 04/13] corrected version of team_com.py --- .../bitbots_team_communication.py | 28 ++++++++++++++----- .../config/team_communication_config.yaml | 2 ++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py b/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py index 1eda840f3..ae21d3d0d 100755 --- a/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py +++ b/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py @@ -50,6 +50,7 @@ def __init__(self): self.socket_communication = SocketCommunication(self.node, self.logger, self.team_id, self.player_id) self.rate: int = self.node.get_parameter("rate").value + self.reduced_rate: int = self.node.get_parameter("reduced_rate").value self.lifetime: int = self.node.get_parameter("lifetime").value self.avg_walking_speed: float = self.node.get_parameter("avg_walking_speed").value self.rate_is_reduced: bool = False @@ -67,7 +68,7 @@ def __init__(self): self.run_spin_in_thread() self.try_to_establish_connection() - self.timer = self.node.create_timer(1 / self.rate, self.send_message, callback_group=MutuallyExclusiveCallbackGroup()) + self.create_timer(self.rate) self.receive_forever() def spin(self): @@ -311,10 +312,8 @@ def is_robot_allowed_to_send_message(self) -> bool: if self.gamestate is not None and not self.gamestate.penalized: return False #if we are close to our message budget, we dont want to continue publishing - if self.gamestate is not None and (self.gamestate.message_budget > 40): - return False - #we dont want to publish messages if only one robot is in a team. (this may never occure since this is the max team size, not the number of active players) - if self.gamestate is not None and self.gamestate.players_per_team == 1: + #the budget smaller 40 as stop definition makes sure we have 10 msg per robot left in case of some delay in the communication with the game controller + if self.gamestate is not None and (self.gamestate.message_budget < 40): return False return True @@ -331,12 +330,27 @@ def convert_to_euler(self, quaternion: Quaternion): return transforms3d.euler.quat2euler([quaternion.w, quaternion.x, quaternion.y, quaternion.z]) def reduce_rate(self): - self.rate = 1 self.timer.cancel() - self.timer = self.node.create_timer(1 / self.rate, self.send_message, callback_group=MutuallyExclusiveCallbackGroup()) + self.create_timer(self.reduced_rate) self.rate_is_reduced = True + self.logger.warning("Team communication: message sending rate is reduced now") + def create_timer(self, rate: int): + self.timer = self.node.create_timer(1 / rate, self.send_message, callback_group=MutuallyExclusiveCallbackGroup()) + def should_reduce_rate(self): + # we are allowed to send 2.5 msg per second on average with each robot (12000 with 4 robots in a 1200 sekonds game) + # the msg_left_linear_rate is the amount of messages we would send if we send exactly with this 2.5 msg per sec per robot rate + # once our actual msg budget is below this linear rate we tend to send more msg then allowed and should reduce our sending rate + if self.game_started_recently(): + return False + + msg_left_linear_rate = (self.gamestate.first_half * 600 + self.gamestate.secs_remaining) * 4 * 2.5 + return msg_left_linear_rate > self.gamestate.message_budget + + def game_started_recently(self): + #true in the first 60 seconds of the game + return self.gamestate.first_half and self.gamestate.secs_remaining > 540 def main(): rclpy.init(args=None) diff --git a/src/bitbots_team_communication/bitbots_team_communication/config/team_communication_config.yaml b/src/bitbots_team_communication/bitbots_team_communication/config/team_communication_config.yaml index b4554bc6a..0c87472a2 100644 --- a/src/bitbots_team_communication/bitbots_team_communication/config/team_communication_config.yaml +++ b/src/bitbots_team_communication/bitbots_team_communication/config/team_communication_config.yaml @@ -18,6 +18,8 @@ team_comm: # Rate of published messages in Hz rate: 2 + # Rate of published messages in Hz, when rate is reduced + reduced_rate: 2 # average walking speed in [m/s] avg_walking_speed: 0.2 From 32c7f1db8f6e99b0861c618269e5d834253ceefd Mon Sep 17 00:00:00 2001 From: Clemens Wulff Date: Sat, 23 May 2026 16:27:09 +0200 Subject: [PATCH 05/13] check for limit in Proto mesage size before sending --- .../bitbots_team_communication.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py b/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py index ae21d3d0d..d6a2ce13b 100755 --- a/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py +++ b/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py @@ -282,8 +282,11 @@ def is_still_valid(time: Optional[TimeMsg]) -> bool: message = self.protocol_converter.convert_to_message(self, msg, is_still_valid) proto_msg = message.SerializeToString() - self.logger.debug(f"Sending msg with size {len(proto_msg)} bytes") - self.socket_communication.send_message(proto_msg) + if(len(proto_msg) > 512): + self.logger.warning(f"Team_com msg not sended, because size {len(proto_msg)} bytes is above the maximum of 512 bytes") + else: + self.logger.debug(f"Sending msg with size {len(proto_msg)} bytes") + self.socket_communication.send_message(proto_msg) def create_empty_message(self, now: Time) -> Proto.Message: message = Proto.Message() From 793f1cde53c07d52e71c5918b5e06cf760fdce17 Mon Sep 17 00:00:00 2001 From: Clemens Wulff Date: Sat, 23 May 2026 16:33:55 +0200 Subject: [PATCH 06/13] corrected reduce rate in config --- .../config/team_communication_config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bitbots_team_communication/bitbots_team_communication/config/team_communication_config.yaml b/src/bitbots_team_communication/bitbots_team_communication/config/team_communication_config.yaml index 0c87472a2..49fc7ae6f 100644 --- a/src/bitbots_team_communication/bitbots_team_communication/config/team_communication_config.yaml +++ b/src/bitbots_team_communication/bitbots_team_communication/config/team_communication_config.yaml @@ -19,7 +19,7 @@ team_comm: # Rate of published messages in Hz rate: 2 # Rate of published messages in Hz, when rate is reduced - reduced_rate: 2 + reduced_rate: 1 # average walking speed in [m/s] avg_walking_speed: 0.2 From 0c0e384b6d8e04ba8578be3eec96ae933ceb2ea9 Mon Sep 17 00:00:00 2001 From: Clemens Wulff Date: Sat, 23 May 2026 19:04:44 +0200 Subject: [PATCH 07/13] apply behaviour changes in DSD and depending decisions to continue playing without team com --- .../capsules/game_status_capsule.py | 6 ++ .../capsules/team_data_capsule.py | 28 +++++++ .../behavior_dsd/decisions/closest_to_ball.py | 18 ++-- .../count_active_players_without_goalie.py | 5 +- .../behavior_dsd/decisions/goalie_active.py | 12 ++- .../decisions/goalie_handling_ball.py | 12 ++- .../decisions/team_com_limit_reached.py | 18 ++++ .../behavior_dsd/main.dsd | 83 +++++++------------ 8 files changed, 118 insertions(+), 64 deletions(-) create mode 100644 src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/team_com_limit_reached.py diff --git a/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/game_status_capsule.py b/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/game_status_capsule.py index 2dcb5a048..753a7655c 100644 --- a/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/game_status_capsule.py +++ b/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/game_status_capsule.py @@ -22,6 +22,7 @@ def __init__(self, node, blackboard=None): self.free_kick_kickoff_team: bool | None = None self.game_controller_stop: bool = False self.last_timestep_whistle_detected: Time | None = None + self.team_com_limit_has_reached: bool = False # publish stopped msg for hcm self.stop_pub = node.create_publisher(Bool, "game_controller/stop_msg", 1) @@ -96,6 +97,9 @@ def received_gamestate(self) -> bool: def get_team_id(self) -> int: return self.team_id + + def get_team_com_limit_has_reached(self) -> bool: + return self.team_com_limit_has_reached def gamestate_callback(self, gamestate_msg: GameState) -> None: if self.gamestate.penalized and not gamestate_msg.penalized: @@ -112,6 +116,8 @@ def gamestate_callback(self, gamestate_msg: GameState) -> None: self.stop_pub.publish(Bool(data=self.game_controller_stop)) + self.team_com_limit_has_reached = gamestate_msg.message_budget < 40 + """Anstoß im Falle von Overtime jetzt erstmal nicht genauer geregelt if ( gamestate_msg.main_state == GameState.STATE_SET diff --git a/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/team_data_capsule.py b/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/team_data_capsule.py index c44d479f1..a945f52e9 100644 --- a/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/team_data_capsule.py +++ b/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/team_data_capsule.py @@ -68,6 +68,16 @@ def __init__(self, node, blackboard): self.data_timeout: float = float(self._node.get_parameter("team_data_timeout").value) self.ball_max_covariance: float = float(self._node.get_parameter("ball_max_covariance").value) + # --- Save informations for handling decisions after team com has switched off --- + # The last rank to ball result before team_com limit was reached + self.last_rank_to_ball_with_team_com: int = -1 + # Was goalie active before team_com limit was reached + self.was_goalie_active: bool = True + # Was goalie handling the ball before team_com limit was reached + self.was_goalie_handling_ball_with_team_com: bool = False + # The last number of active field players before team_com limit was reached + self.last_number_of_active_players: int = -1 + @cached_capsule_function def time(self) -> Time: """Returns the current time of the node, this is its own function so it can be cached during the decision loop.""" @@ -93,7 +103,9 @@ def is_goalie_handling_ball(self) -> bool: and data.strategy.role == Strategy.ROLE_GOALIE and data.strategy.action in [Strategy.ACTION_GOING_TO_BALL, Strategy.ACTION_KICKING] ): + self.was_goalie_handling_ball_with_team_com = True return True + self.was_goalie_handling_ball_with_team_com = False return False @cached_capsule_function @@ -138,7 +150,9 @@ def team_rank_to_ball( ) for rank, distance in enumerate(sorted(distances)): if own_ball_distance < distance: + self.last_rank_to_ball_with_team_com = rank + 1 return rank + 1 + self.last_rank_to_ball_with_team_com = len(distances) + 1 return len(distances) + 1 def set_action(self, action: int) -> None: @@ -151,6 +165,18 @@ def set_action(self, action: int) -> None: def get_action(self) -> tuple[int, float]: return self.strategy.action, self.action_update + + def get_was_goalie_handling_ball(self) -> bool: + return self.was_goalie_handling_ball_with_team_com + + def get_was_goalie_active(self) -> bool: + return self.was_goalie_active + + def get_last_rank_with_team_com(self) -> int: + return self.last_rank_to_ball_with_team_com + + def get_last_number_active_player(self) -> int: + return self.last_number_of_active_players def set_role(self, role: Literal["goalie", "offense", "defense"]) -> None: """Set the role of this robot in the team @@ -195,6 +221,7 @@ def is_not_goalie(team_data: TeamData) -> bool: team_data_infos = filter(is_not_goalie, team_data_infos) # type: ignore[assignment] # Count valid team data infos (aka robots with valid team data) + self.last_number_of_active_players = sum(map(self.is_valid, team_data_infos)) return sum(map(self.is_valid, team_data_infos)) @cached_capsule_function @@ -209,6 +236,7 @@ def is_a_goalie(team_data: TeamData) -> bool: team_data_infos = filter(is_a_goalie, team_data_infos) # type: ignore[assignment] # Count valid team data infos (aka robots with valid team data) + self.was_goalie_active = sum(map(self.is_valid, team_data_infos)) == 1 return sum(map(self.is_valid, team_data_infos)) == 1 def get_own_time_to_ball(self) -> float: diff --git a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/closest_to_ball.py b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/closest_to_ball.py index c3b7766b6..426956464 100644 --- a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/closest_to_ball.py +++ b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/closest_to_ball.py @@ -28,9 +28,12 @@ def __init__(self, blackboard, dsd, parameters): super().__init__(blackboard, dsd, parameters) def perform(self, reevaluate=False): - my_time_to_ball = self.blackboard.team_data.get_own_time_to_ball() - rank = self.blackboard.team_data.team_rank_to_ball(my_time_to_ball, count_goalies=True, use_time_to_ball=True) - self.publish_debug_data("time to ball", my_time_to_ball) + if self.blackboard.gamstate.get_team_com_limit_has_reached(): + rank = self.blackboard.team_data.get_last_rank_with_team_com() + else: + my_time_to_ball = self.blackboard.team_data.get_own_time_to_ball() + rank = self.blackboard.team_data.team_rank_to_ball(my_time_to_ball, count_goalies=True, use_time_to_ball=True) + self.publish_debug_data("time to ball", my_time_to_ball) self.publish_debug_data("Rank to ball", rank) if rank == 1: return "YES" @@ -47,9 +50,12 @@ def __init__(self, blackboard, dsd, parameters): super().__init__(blackboard, dsd, parameters) def perform(self, reevaluate=False): - my_time_to_ball = self.blackboard.team_data.get_own_time_to_ball() - rank = self.blackboard.team_data.team_rank_to_ball(my_time_to_ball, count_goalies=False, use_time_to_ball=True) - self.publish_debug_data("time to ball", my_time_to_ball) + if self.blackboard.gamstate.get_team_com_limit_has_reached(): + rank = self.blackboard.team_data.get_last_rank_with_team_com() + else: + my_time_to_ball = self.blackboard.team_data.get_own_time_to_ball() + rank = self.blackboard.team_data.team_rank_to_ball(my_time_to_ball, count_goalies=False, use_time_to_ball=True) + self.publish_debug_data("time to ball", my_time_to_ball) self.publish_debug_data("Rank to ball", rank) if rank == 1: return "FIRST" diff --git a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/count_active_players_without_goalie.py b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/count_active_players_without_goalie.py index 1440883e7..7e4a5468e 100644 --- a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/count_active_players_without_goalie.py +++ b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/count_active_players_without_goalie.py @@ -13,7 +13,10 @@ def __init__(self, blackboard, dsd, parameters): super().__init__(blackboard, dsd, parameters) def perform(self, reevaluate=False): - number_of_active_teammates = self.blackboard.team_data.get_number_of_active_field_players(False) + if self.blackboard.gamstate.get_team_com_limit_has_reached(): + number_of_active_teammates = self.blackboard.team_data.get_last_number_active_player() + else: + number_of_active_teammates = self.blackboard.team_data.get_number_of_active_field_players(False) self.publish_debug_data("Number of active Teammates", number_of_active_teammates) if number_of_active_teammates == 0: return "ZERO" diff --git a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/goalie_active.py b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/goalie_active.py index c0783318c..08e4a8f39 100644 --- a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/goalie_active.py +++ b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/goalie_active.py @@ -13,10 +13,16 @@ def __init__(self, blackboard, dsd, parameters): super().__init__(blackboard, dsd, parameters) def perform(self, reevaluate=False): - if self.blackboard.team_data.get_is_goalie_active(): - return "YES" + if self.blackboard.gamstate.get_team_com_limit_has_reached(): + if self.blackboard.team_data.get_was_goalie_active(): + return "YES" + else: + return "NO" else: - return "NO" + if self.blackboard.team_data.get_is_goalie_active(): + return "YES" + else: + return "NO" def get_reevaluate(self): return True diff --git a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/goalie_handling_ball.py b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/goalie_handling_ball.py index fee37dcd4..83b007348 100644 --- a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/goalie_handling_ball.py +++ b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/goalie_handling_ball.py @@ -12,10 +12,16 @@ def perform(self, reevaluate=False): """ It is determined if the goalie is currently going towards the ball """ - if self.blackboard.team_data.is_goalie_handling_ball(): - return "YES" + if self.blackboard.gamstate.get_team_com_limit_has_reached(): + if self.blackboard.team_data.get_was_goalie_handling_ball(): + return "YES" + else: + return "NO" else: - return "NO" + if self.blackboard.team_data.is_goalie_handling_ball(): + return "YES" + else: + return "NO" def get_reevaluate(self): return True diff --git a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/team_com_limit_reached.py b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/team_com_limit_reached.py new file mode 100644 index 000000000..ca9ca85c8 --- /dev/null +++ b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/team_com_limit_reached.py @@ -0,0 +1,18 @@ +from bitbots_blackboard.body_blackboard import BodyBlackboard +from dynamic_stack_decider.abstract_decision_element import AbstractDecisionElement + + +class TeamComLimitReached(AbstractDecisionElement): + blackboard: BodyBlackboard + + def __init__(self, blackboard, dsd, parameters): + super().__init__(blackboard, dsd, parameters) + + def perform(self, reevaluate=False): + if self.blackboard.gamstate.get_team_com_limit_has_reached(): + return "YES" + else: + return "NO" + + def get_reevaluate(self): + return True diff --git a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd index 789a14102..4bd09e7f4 100644 --- a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd +++ b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd @@ -76,11 +76,13 @@ $GoalieActive NO --> @LookAtFieldFeatures, @ChangeAction + action:positioning, @GoToBlockPosition #SupporterRole -$PassStarted - YES --> $BallSeen - YES --> @TrackBall, @ChangeAction + action:positioning, @AvoidBallActive, @GoToPassAcceptPosition - NO --> @LookAtFieldFeatures, @ChangeAction + action:positioning, @AvoidBallActive, @GoToPassAcceptPosition - NO --> @LookAtFieldFeatures, @ChangeAction + action:positioning, @AvoidBallActive, @GoToPassPreparePosition +$TeamComLimitReached + YES --> @LookAtFieldFeatures, @ChangeAction + action:positioning, @AvoidBallActive, @GoToPassPreparePosition + NO --> $PassStarted + YES --> $BallSeen + YES --> @TrackBall, @ChangeAction + action:positioning, @AvoidBallActive, @GoToPassAcceptPosition + NO --> @LookAtFieldFeatures, @ChangeAction + action:positioning, @AvoidBallActive, @GoToPassAcceptPosition + NO --> @LookAtFieldFeatures, @ChangeAction + action:positioning, @AvoidBallActive, @GoToPassPreparePosition #PenaltyShootoutBehavior $SecondaryStateTeamDecider @@ -108,27 +110,6 @@ $DoOnce SECOND --> @ChangeAction + action:positioning, @LookAtFieldFeatures, @AvoidBallActive, @GoToDefensePosition + mode:freekick_second THIRD --> @ChangeAction + action:positioning, @LookAtFieldFeatures, @AvoidBallActive, @GoToDefensePosition -#PlacingWithoutTeamCom -$DoOnce - NOT_DONE --> @ForgetBall, @LookAtFieldFeatures - DONE --> $ConfigRole - GOALIE --> @ChangeAction + action:positioning, @LookAtFieldFeatures, @GoToBlockPosition - ELSE --> $BallSeen - NO --> $SecondaryStateTeamDecider - OUR --> #SearchBall - OTHER --> @AvoidBallActive, @LookAtFieldFeatures, @WalkInPlace + duration:2, @GoToRelativePosition + x:1 + y:0 + t:0, @Stand - //TODO - YES --> $NumberPenalizedTeamMates - ZERO - ONE - TWO - THREE - FOUR - YES --> $RankToBallNoGoalieWithoutTeamCom - FIRST --> @ChangeAction + action:positioning, @LookAtFieldFeatures, @AvoidBallActive, @GoToDefensePosition + mode:freekick_first - SECOND --> @ChangeAction + action:positioning, @LookAtFieldFeatures, @AvoidBallActive, @GoToDefensePosition + mode:freekick_second - THIRD --> @ChangeAction + action:positioning, @LookAtFieldFeatures, @AvoidBallActive, @GoToDefensePosition - #Init @Stand + duration:0.1 + r:false, @ChangeAction + action:waiting, @LookForward, @Stand @@ -142,14 +123,23 @@ $BallSeen NO_FREEKICK --> #Placing YES --> $ConfigRole GOALIE --> #GoalieBehavior - ELSE --> $CountActiveRobotsWithoutGoalie - ONE --> $RankToBallNoGoalie - FIRST --> #StrikerRole - SECOND --> #DefensePositioning - ELSE --> $RankToBallNoGoalie - FIRST --> #StrikerRole - SECOND --> #SupporterRole - THIRD --> #DefensePositioning + ELSE --> $TeamComLimitReached //this decision is only for better visualization + NO -- > $CountActiveRobotsWithoutGoalie + ONE --> $RankToBallNoGoalie + FIRST --> #StrikerRole + SECOND --> #DefensePositioning + ELSE --> $RankToBallNoGoalie + FIRST --> #StrikerRole + SECOND --> #SupporterRole + THIRD --> #DefensePositioning + YES --> $CountActiveRobotsWithoutGoalie + ONE --> $RankToBallNoGoalie + FIRST --> #StrikerRole + SECOND --> #DefensePositioning + ELSE --> $RankToBallNoGoalie + FIRST --> #StrikerRole + SECOND --> #SupporterRole + THIRD --> #DefensePositioning #NormalBehaviorWithoutTeamCom $BallSeen @@ -171,23 +161,14 @@ $BallSeen THIRD --> #DefensePositioning #PlayingBehavior -$TeamComLimitReached - YES --> $SecondaryStateDecider - PENALTYSHOOT --> #PenaltyShootoutBehavior - TIMEOUT --> #StandAndLook - ELSE --> $SecondaryStateTeamDecider - OUR --> #NormalBehaviorWithoutTeamCom - OTHER --> #PlacingWithoutTeamCom - NORMAL --> #NormalBehaviorWithoutTeamCom - OVERTIME --> #NormalBehaviorWithoutTeamCom - NO --> $SecondaryStateDecider - PENALTYSHOOT --> #PenaltyShootoutBehavior - TIMEOUT --> #StandAndLook - ELSE --> $SecondaryStateTeamDecider - OUR --> #NormalBehavior - OTHER --> #Placing - NORMAL --> #NormalBehavior - OVERTIME --> #NormalBehavior +$SecondaryStateDecider + PENALTYSHOOT --> #PenaltyShootoutBehavior + TIMEOUT --> #StandAndLook + ELSE --> $SecondaryStateTeamDecider + OUR --> #NormalBehavior + OTHER --> #Placing + NORMAL --> #NormalBehavior + OVERTIME --> #NormalBehavior -->BodyBehavior $IsPenalized From dc7af9b3510a46c3527a1d0b478c28fe8b02dd02 Mon Sep 17 00:00:00 2001 From: Clemens Wulff Date: Sun, 24 May 2026 14:55:32 +0200 Subject: [PATCH 08/13] handle returning robots when penalty ends after team com limit --- .../bitbots_blackboard/capsules/game_status_capsule.py | 6 ++++++ .../behavior_dsd/decisions/is_penalized.py | 3 +++ .../bitbots_body_behavior/behavior_dsd/main.dsd | 1 + 3 files changed, 10 insertions(+) diff --git a/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/game_status_capsule.py b/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/game_status_capsule.py index 753a7655c..46ceead70 100644 --- a/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/game_status_capsule.py +++ b/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/game_status_capsule.py @@ -23,6 +23,7 @@ def __init__(self, node, blackboard=None): self.game_controller_stop: bool = False self.last_timestep_whistle_detected: Time | None = None self.team_com_limit_has_reached: bool = False + self.upenalized_after_team_com_stop: bool = False # publish stopped msg for hcm self.stop_pub = node.create_publisher(Bool, "game_controller/stop_msg", 1) @@ -86,6 +87,9 @@ def get_seconds_since_unpenalized(self) -> float: def get_is_penalized(self) -> bool: return self.gamestate.penalized + def get_upenalized_after_team_com_stop(self) -> bool: + return self.upenalized_after_team_com_stop + def get_penalized_team_mates(self) -> int: return self.gamestate.team_mates_with_penalty @@ -104,6 +108,8 @@ def get_team_com_limit_has_reached(self) -> bool: def gamestate_callback(self, gamestate_msg: GameState) -> None: if self.gamestate.penalized and not gamestate_msg.penalized: self.unpenalized_time = self._node.get_clock().now().nanoseconds / 1e9 + if self.team_com_limit_has_reached: + self.upenalized_after_team_com_stop = True if gamestate_msg.own_score > self.gamestate.own_score: self.last_goal_from_us_time = self._node.get_clock().now().nanoseconds / 1e9 diff --git a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/is_penalized.py b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/is_penalized.py index 48842970a..7498f81d6 100644 --- a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/is_penalized.py +++ b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/is_penalized.py @@ -15,6 +15,9 @@ def perform(self, reevaluate=False): self.publish_debug_data("Seconds since unpenalized", self.blackboard.gamestate.get_seconds_since_unpenalized()) if self.blackboard.gamestate.get_is_penalized(): return "YES" + elif self.blackboard.gamestate.get_upenalized_after_team_com_stop(): + self.publish_debug_data("Reason", "Unpenalized after team com limit") + return "UNPENALIZED_AFTER_TEAM_COM_LIMIT" elif self.blackboard.gamestate.get_seconds_since_unpenalized() < 1: self.publish_debug_data("Reason", "Just unpenalized") return "JUST_UNPENALIZED" diff --git a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd index 4bd09e7f4..0a8db36fc 100644 --- a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd +++ b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd @@ -176,6 +176,7 @@ $IsPenalized JUST_UNPENALIZED --> $GameStateDecider INITIAL --> #Init ELSE --> #GetWalkreadyAndLocalize + UNPENALIZED_AFTER_TEAM_COM_LIMIT --> #DoNothing NO --> $GameStateDecider INITIAL --> #Init READY --> $AnyGoalScoreRecently + time:50 From 6071a917bae14d588d98b420f42166bdf2cfa78b Mon Sep 17 00:00:00 2001 From: Jan Gutsche Date: Mon, 25 May 2026 12:30:55 +0200 Subject: [PATCH 09/13] Fix typo in gamestate references across decision classes --- .../behavior_dsd/decisions/closest_to_ball.py | 12 ++++++++---- .../decisions/count_active_players_without_goalie.py | 2 +- .../behavior_dsd/decisions/goalie_active.py | 2 +- .../behavior_dsd/decisions/goalie_handling_ball.py | 2 +- .../behavior_dsd/decisions/team_com_limit_reached.py | 2 +- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/closest_to_ball.py b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/closest_to_ball.py index 426956464..d418d2468 100644 --- a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/closest_to_ball.py +++ b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/closest_to_ball.py @@ -28,11 +28,13 @@ def __init__(self, blackboard, dsd, parameters): super().__init__(blackboard, dsd, parameters) def perform(self, reevaluate=False): - if self.blackboard.gamstate.get_team_com_limit_has_reached(): + if self.blackboard.gamestate.get_team_com_limit_has_reached(): rank = self.blackboard.team_data.get_last_rank_with_team_com() else: my_time_to_ball = self.blackboard.team_data.get_own_time_to_ball() - rank = self.blackboard.team_data.team_rank_to_ball(my_time_to_ball, count_goalies=True, use_time_to_ball=True) + rank = self.blackboard.team_data.team_rank_to_ball( + my_time_to_ball, count_goalies=True, use_time_to_ball=True + ) self.publish_debug_data("time to ball", my_time_to_ball) self.publish_debug_data("Rank to ball", rank) if rank == 1: @@ -50,11 +52,13 @@ def __init__(self, blackboard, dsd, parameters): super().__init__(blackboard, dsd, parameters) def perform(self, reevaluate=False): - if self.blackboard.gamstate.get_team_com_limit_has_reached(): + if self.blackboard.gamestate.get_team_com_limit_has_reached(): rank = self.blackboard.team_data.get_last_rank_with_team_com() else: my_time_to_ball = self.blackboard.team_data.get_own_time_to_ball() - rank = self.blackboard.team_data.team_rank_to_ball(my_time_to_ball, count_goalies=False, use_time_to_ball=True) + rank = self.blackboard.team_data.team_rank_to_ball( + my_time_to_ball, count_goalies=False, use_time_to_ball=True + ) self.publish_debug_data("time to ball", my_time_to_ball) self.publish_debug_data("Rank to ball", rank) if rank == 1: diff --git a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/count_active_players_without_goalie.py b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/count_active_players_without_goalie.py index 7e4a5468e..5a31fc5fd 100644 --- a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/count_active_players_without_goalie.py +++ b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/count_active_players_without_goalie.py @@ -13,7 +13,7 @@ def __init__(self, blackboard, dsd, parameters): super().__init__(blackboard, dsd, parameters) def perform(self, reevaluate=False): - if self.blackboard.gamstate.get_team_com_limit_has_reached(): + if self.blackboard.gamestate.get_team_com_limit_has_reached(): number_of_active_teammates = self.blackboard.team_data.get_last_number_active_player() else: number_of_active_teammates = self.blackboard.team_data.get_number_of_active_field_players(False) diff --git a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/goalie_active.py b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/goalie_active.py index 08e4a8f39..2b29488a4 100644 --- a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/goalie_active.py +++ b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/goalie_active.py @@ -13,7 +13,7 @@ def __init__(self, blackboard, dsd, parameters): super().__init__(blackboard, dsd, parameters) def perform(self, reevaluate=False): - if self.blackboard.gamstate.get_team_com_limit_has_reached(): + if self.blackboard.gamestate.get_team_com_limit_has_reached(): if self.blackboard.team_data.get_was_goalie_active(): return "YES" else: diff --git a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/goalie_handling_ball.py b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/goalie_handling_ball.py index 83b007348..0dc512972 100644 --- a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/goalie_handling_ball.py +++ b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/goalie_handling_ball.py @@ -12,7 +12,7 @@ def perform(self, reevaluate=False): """ It is determined if the goalie is currently going towards the ball """ - if self.blackboard.gamstate.get_team_com_limit_has_reached(): + if self.blackboard.gamestate.get_team_com_limit_has_reached(): if self.blackboard.team_data.get_was_goalie_handling_ball(): return "YES" else: diff --git a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/team_com_limit_reached.py b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/team_com_limit_reached.py index ca9ca85c8..dfd2a6c34 100644 --- a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/team_com_limit_reached.py +++ b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/team_com_limit_reached.py @@ -9,7 +9,7 @@ def __init__(self, blackboard, dsd, parameters): super().__init__(blackboard, dsd, parameters) def perform(self, reevaluate=False): - if self.blackboard.gamstate.get_team_com_limit_has_reached(): + if self.blackboard.gamestate.get_team_com_limit_has_reached(): return "YES" else: return "NO" From 0b5294e48a514bd15213a9ca70ff0738a100cfc6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 10:42:10 +0000 Subject: [PATCH 10/13] Address PR review feedback for team comm and behavior fallback Agent-Logs-Url: https://github.com/bit-bots/bitbots_main/sessions/2ea79016-c148-4fe2-ac42-4250253e549b Co-authored-by: jaagut <34797331+jaagut@users.noreply.github.com> --- .../capsules/game_status_capsule.py | 8 +++--- .../capsules/team_data_capsule.py | 10 ++++--- .../behavior_dsd/decisions/is_penalized.py | 2 +- .../decisions/number_penalized_players.py | 3 +-- .../behavior_dsd/main.dsd | 11 ++------ .../bitbots_team_communication.py | 27 ++++++++++++------- .../config/team_communication_config.yaml | 2 ++ 7 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/game_status_capsule.py b/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/game_status_capsule.py index 33bc9fbfa..962b1c31c 100644 --- a/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/game_status_capsule.py +++ b/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/game_status_capsule.py @@ -23,7 +23,7 @@ def __init__(self, node, blackboard=None): self.game_controller_stop: bool = False self.last_timestep_whistle_detected: Time | None = None self.team_com_limit_has_reached: bool = False - self.upenalized_after_team_com_stop: bool = False + self.unpenalized_after_team_com_stop: bool = False # publish stopped msg for hcm self.stop_pub = node.create_publisher(Bool, "game_controller/stop_msg", 1) @@ -87,8 +87,8 @@ def get_seconds_since_unpenalized(self) -> float: def get_is_penalized(self) -> bool: return self.gamestate.penalized - def get_upenalized_after_team_com_stop(self) -> bool: - return self.upenalized_after_team_com_stop + def get_unpenalized_after_team_com_stop(self) -> bool: + return self.unpenalized_after_team_com_stop def get_penalized_team_mates(self) -> int: return self.gamestate.team_mates_with_penalty @@ -109,7 +109,7 @@ def gamestate_callback(self, gamestate_msg: GameState) -> None: if self.gamestate.penalized and not gamestate_msg.penalized: self.unpenalized_time = self._node.get_clock().now().nanoseconds / 1e9 if self.team_com_limit_has_reached: - self.upenalized_after_team_com_stop = True + self.unpenalized_after_team_com_stop = True if gamestate_msg.own_score > self.gamestate.own_score: self.last_goal_from_us_time = self._node.get_clock().now().nanoseconds / 1e9 diff --git a/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/team_data_capsule.py b/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/team_data_capsule.py index a945f52e9..bc54bad4f 100644 --- a/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/team_data_capsule.py +++ b/src/bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/team_data_capsule.py @@ -221,8 +221,9 @@ def is_not_goalie(team_data: TeamData) -> bool: team_data_infos = filter(is_not_goalie, team_data_infos) # type: ignore[assignment] # Count valid team data infos (aka robots with valid team data) - self.last_number_of_active_players = sum(map(self.is_valid, team_data_infos)) - return sum(map(self.is_valid, team_data_infos)) + active_players = sum(map(self.is_valid, team_data_infos)) + self.last_number_of_active_players = active_players + return active_players @cached_capsule_function def get_is_goalie_active(self) -> bool: @@ -236,8 +237,9 @@ def is_a_goalie(team_data: TeamData) -> bool: team_data_infos = filter(is_a_goalie, team_data_infos) # type: ignore[assignment] # Count valid team data infos (aka robots with valid team data) - self.was_goalie_active = sum(map(self.is_valid, team_data_infos)) == 1 - return sum(map(self.is_valid, team_data_infos)) == 1 + goalie_count = sum(map(self.is_valid, team_data_infos)) + self.was_goalie_active = goalie_count == 1 + return goalie_count == 1 def get_own_time_to_ball(self) -> float: return self.own_time_to_ball diff --git a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/is_penalized.py b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/is_penalized.py index 7498f81d6..19397cf4b 100644 --- a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/is_penalized.py +++ b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/is_penalized.py @@ -15,7 +15,7 @@ def perform(self, reevaluate=False): self.publish_debug_data("Seconds since unpenalized", self.blackboard.gamestate.get_seconds_since_unpenalized()) if self.blackboard.gamestate.get_is_penalized(): return "YES" - elif self.blackboard.gamestate.get_upenalized_after_team_com_stop(): + elif self.blackboard.gamestate.get_unpenalized_after_team_com_stop(): self.publish_debug_data("Reason", "Unpenalized after team com limit") return "UNPENALIZED_AFTER_TEAM_COM_LIMIT" elif self.blackboard.gamestate.get_seconds_since_unpenalized() < 1: diff --git a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/number_penalized_players.py b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/number_penalized_players.py index e6b04cba9..e63f5254e 100644 --- a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/number_penalized_players.py +++ b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/number_penalized_players.py @@ -1,6 +1,5 @@ from bitbots_blackboard.body_blackboard import BodyBlackboard from dynamic_stack_decider.abstract_decision_element import AbstractDecisionElement -from game_controller_hsl_interfaces.msg import GameState class NumberPenalizedTeamMates(AbstractDecisionElement): @@ -33,7 +32,7 @@ def get_reevaluate(self): Game state can change during the game """ return True - + class NumberPenalizedRivals(AbstractDecisionElement): blackboard: BodyBlackboard diff --git a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd index 143fba91c..4dc6fae64 100644 --- a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd +++ b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd @@ -123,15 +123,7 @@ $BallSeen YES --> $ConfigRole GOALIE --> #GoalieBehavior ELSE --> $TeamComLimitReached //this decision is only for better visualization - NO -- > $CountActiveRobotsWithoutGoalie - ONE --> $RankToBallNoGoalie - FIRST --> #StrikerRole - SECOND --> #DefensePositioning - ELSE --> $RankToBallNoGoalie - FIRST --> #StrikerRole - SECOND --> #SupporterRole - THIRD --> #DefensePositioning - YES --> $CountActiveRobotsWithoutGoalie + NO --> $CountActiveRobotsWithoutGoalie ONE --> $RankToBallNoGoalie FIRST --> #StrikerRole SECOND --> #DefensePositioning @@ -139,6 +131,7 @@ $BallSeen FIRST --> #StrikerRole SECOND --> #SupporterRole THIRD --> #DefensePositioning + YES --> #NormalBehaviorWithoutTeamCom #NormalBehaviorWithoutTeamCom $BallSeen diff --git a/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py b/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py index d6a2ce13b..6947c1a39 100755 --- a/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py +++ b/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py @@ -51,6 +51,7 @@ def __init__(self): self.rate: int = self.node.get_parameter("rate").value self.reduced_rate: int = self.node.get_parameter("reduced_rate").value + self.max_message_size: int = self.node.get_parameter("max_message_size").value self.lifetime: int = self.node.get_parameter("lifetime").value self.avg_walking_speed: float = self.node.get_parameter("avg_walking_speed").value self.rate_is_reduced: bool = False @@ -265,10 +266,8 @@ def handle_message(self, string_message: bytes): self.team_data_publisher.publish(team_data) def send_message(self): - - if not self.rate_is_reduced: - if self.gamestate is not None and self.gamestate.secs_remaining > 180 and (self.gamestate.message_budget / self.gamestate.secs_remaining) < 11.2: - self.reduce_rate() + if not self.rate_is_reduced and self.should_reduce_rate(): + self.reduce_rate() if not self.is_robot_allowed_to_send_message(): self.logger.debug("Robot is not allowed to send message") @@ -282,10 +281,14 @@ def is_still_valid(time: Optional[TimeMsg]) -> bool: message = self.protocol_converter.convert_to_message(self, msg, is_still_valid) proto_msg = message.SerializeToString() - if(len(proto_msg) > 512): - self.logger.warning(f"Team_com msg not sended, because size {len(proto_msg)} bytes is above the maximum of 512 bytes") + message_size = len(proto_msg) + if message_size > self.max_message_size: + self.logger.warning( + f"Team_com msg not sent, because size {message_size} bytes is above the maximum of " + f"{self.max_message_size} bytes" + ) else: - self.logger.debug(f"Sending msg with size {len(proto_msg)} bytes") + self.logger.debug(f"Sending msg with size {message_size} bytes") self.socket_communication.send_message(proto_msg) def create_empty_message(self, now: Time) -> Proto.Message: @@ -342,16 +345,22 @@ def create_timer(self, rate: int): self.timer = self.node.create_timer(1 / rate, self.send_message, callback_group=MutuallyExclusiveCallbackGroup()) def should_reduce_rate(self): + if self.gamestate is None: + return False + # we are allowed to send 2.5 msg per second on average with each robot (12000 with 4 robots in a 1200 sekonds game) # the msg_left_linear_rate is the amount of messages we would send if we send exactly with this 2.5 msg per sec per robot rate # once our actual msg budget is below this linear rate we tend to send more msg then allowed and should reduce our sending rate if self.game_started_recently(): return False - + msg_left_linear_rate = (self.gamestate.first_half * 600 + self.gamestate.secs_remaining) * 4 * 2.5 return msg_left_linear_rate > self.gamestate.message_budget - + def game_started_recently(self): + if self.gamestate is None: + return False + #true in the first 60 seconds of the game return self.gamestate.first_half and self.gamestate.secs_remaining > 540 diff --git a/src/bitbots_team_communication/bitbots_team_communication/config/team_communication_config.yaml b/src/bitbots_team_communication/bitbots_team_communication/config/team_communication_config.yaml index 727d3875d..83c4b3371 100644 --- a/src/bitbots_team_communication/bitbots_team_communication/config/team_communication_config.yaml +++ b/src/bitbots_team_communication/bitbots_team_communication/config/team_communication_config.yaml @@ -20,6 +20,8 @@ team_comm: rate: 2 # Rate of published messages in Hz, when rate is reduced reduced_rate: 1 + # Maximum allowed serialized message size in bytes + max_message_size: 512 # average walking speed in [m/s] avg_walking_speed: 0.2 From b90edc8e5e7f593bac8b8bc686498c36f582d41e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 10:43:18 +0000 Subject: [PATCH 11/13] Refine team comm rate helper constants naming Agent-Logs-Url: https://github.com/bit-bots/bitbots_main/sessions/2ea79016-c148-4fe2-ac42-4250253e549b Co-authored-by: jaagut <34797331+jaagut@users.noreply.github.com> --- .../bitbots_team_communication.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py b/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py index 6947c1a39..60623ac85 100755 --- a/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py +++ b/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py @@ -348,13 +348,19 @@ def should_reduce_rate(self): if self.gamestate is None: return False + half_duration_seconds = 600 + team_size = 4 + msgs_per_second_per_robot = 2.5 + # we are allowed to send 2.5 msg per second on average with each robot (12000 with 4 robots in a 1200 sekonds game) # the msg_left_linear_rate is the amount of messages we would send if we send exactly with this 2.5 msg per sec per robot rate # once our actual msg budget is below this linear rate we tend to send more msg then allowed and should reduce our sending rate if self.game_started_recently(): return False - msg_left_linear_rate = (self.gamestate.first_half * 600 + self.gamestate.secs_remaining) * 4 * 2.5 + msg_left_linear_rate = ( + self.gamestate.first_half * half_duration_seconds + self.gamestate.secs_remaining + ) * team_size * msgs_per_second_per_robot return msg_left_linear_rate > self.gamestate.message_budget def game_started_recently(self): From 0eccd8d354ff370f25ea32025bf46fdcf0a04c6b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 10:44:12 +0000 Subject: [PATCH 12/13] Polish team comm helper comments Agent-Logs-Url: https://github.com/bit-bots/bitbots_main/sessions/2ea79016-c148-4fe2-ac42-4250253e549b Co-authored-by: jaagut <34797331+jaagut@users.noreply.github.com> --- .../bitbots_team_communication/bitbots_team_communication.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py b/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py index 60623ac85..75a2e80dd 100755 --- a/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py +++ b/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py @@ -352,7 +352,7 @@ def should_reduce_rate(self): team_size = 4 msgs_per_second_per_robot = 2.5 - # we are allowed to send 2.5 msg per second on average with each robot (12000 with 4 robots in a 1200 sekonds game) + # We are allowed to send 2.5 messages per second on average with each robot (12000 with 4 robots in a 1200 seconds game). # the msg_left_linear_rate is the amount of messages we would send if we send exactly with this 2.5 msg per sec per robot rate # once our actual msg budget is below this linear rate we tend to send more msg then allowed and should reduce our sending rate if self.game_started_recently(): @@ -367,7 +367,7 @@ def game_started_recently(self): if self.gamestate is None: return False - #true in the first 60 seconds of the game + # True in the first 60 seconds of the game. return self.gamestate.first_half and self.gamestate.secs_remaining > 540 def main(): From c1b6f509069e383530dd69bdcab75a57576f7709 Mon Sep 17 00:00:00 2001 From: Clemens Wulff Date: Mon, 25 May 2026 15:25:52 +0200 Subject: [PATCH 13/13] improvements based on copilot feedback, dsd without #NormalBehavior AfterTeamCom and correct logic for is_allowed_to_send_message --- .../behavior_dsd/main.dsd | 30 ++++++------------- .../bitbots_team_communication.py | 16 +++++----- 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd index 4dc6fae64..0b073a865 100644 --- a/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd +++ b/src/bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/main.dsd @@ -122,7 +122,7 @@ $BallSeen NO_FREEKICK --> #Placing YES --> $ConfigRole GOALIE --> #GoalieBehavior - ELSE --> $TeamComLimitReached //this decision is only for better visualization + ELSE --> $TeamComLimitReached //this decision is only for better visualization in rqt NO --> $CountActiveRobotsWithoutGoalie ONE --> $RankToBallNoGoalie FIRST --> #StrikerRole @@ -131,26 +131,14 @@ $BallSeen FIRST --> #StrikerRole SECOND --> #SupporterRole THIRD --> #DefensePositioning - YES --> #NormalBehaviorWithoutTeamCom - -#NormalBehaviorWithoutTeamCom -$BallSeen - NO --> $ConfigRole - GOALIE --> #RolePositionWithPause - ELSE --> #SearchBall - YES --> $KickOffTimeUp - NO_NORMAL --> #StandAndLook - NO_FREEKICK --> #PlacingWithoutTeamCom - YES --> $ConfigRole - GOALIE --> #GoalieBehavior - ELSE --> $CountActiveRobotsWithoutGoalie - ONE --> $RankToBallNoGoalie - FIRST --> #StrikerRole - SECOND --> #DefensePositioning - ELSE --> $RankToBallNoGoalie - FIRST --> #StrikerRole - SECOND --> #SupporterRole - THIRD --> #DefensePositioning + YES --> $CountActiveRobotsWithoutGoalie + ONE --> $RankToBallNoGoalie + FIRST --> #StrikerRole + SECOND --> #DefensePositioning + ELSE --> $RankToBallNoGoalie + FIRST --> #StrikerRole + SECOND --> #SupporterRole + THIRD --> #DefensePositioning #PlayingBehavior $SecondaryStateDecider diff --git a/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py b/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py index 75a2e80dd..1d65dbe39 100755 --- a/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py +++ b/src/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication.py @@ -314,13 +314,15 @@ def should_message_be_discarded(self, message: Proto.Message) -> bool: return is_own_message or is_message_from_oposite_team def is_robot_allowed_to_send_message(self) -> bool: - #a penalized robot doesn't need to publish - if self.gamestate is not None and not self.gamestate.penalized: - return False - #if we are close to our message budget, we dont want to continue publishing - #the budget smaller 40 as stop definition makes sure we have 10 msg per robot left in case of some delay in the communication with the game controller - if self.gamestate is not None and (self.gamestate.message_budget < 40): - return False + + if self.gamestate is not None: + #a penalized robot doesn't need to publish + if self.gamestate.penalized: + return False + #if we are close to our message budget, we dont want to continue publishing + #the budget smaller 40 as stop definition makes sure we have 10 msg per robot left in case of some delay in the communication with the game controller + if self.gamestate.message_budget < 40: + return False return True