Skip to content

Commit 4a88c7b

Browse files
author
Zhaoyu Ji
committed
Merge branch 'main' into faster_kick
2 parents 38305d1 + c88ae76 commit 4a88c7b

File tree

152 files changed

+1067
-866
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

152 files changed

+1067
-866
lines changed

.vscode/settings.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -214,16 +214,19 @@
214214
"regex": "cpp",
215215
"future": "cpp",
216216
"*.ipp": "cpp",
217-
"span": "cpp"
217+
"span": "cpp",
218+
"ranges": "cpp"
218219
},
219220
// Tell the ROS extension where to find the setup.bash
220221
// This also utilizes the COLCON_WS environment variable, which needs to be set
221222
"ros.distro": "iron",
222223
"search.useIgnoreFiles": false,
223224
"python.autoComplete.extraPaths": [
225+
"/opt/openrobots/lib/python3.10/site-packages",
224226
"/opt/ros/iron/lib/python3.10/site-packages"
225227
],
226228
"python.analysis.extraPaths": [
229+
"/opt/openrobots/lib/python3.10/site-packages",
227230
"/opt/ros/iron/lib/python3.10/site-packages"
228231
],
229232
"cmake.configureOnOpen": false,

bitbots_behavior/bitbots_blackboard/CMakeLists.txt

+12-7
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@ if(NOT CMAKE_CXX_STANDARD)
66
set(CMAKE_CXX_STANDARD 17)
77
endif()
88

9-
find_package(bio_ik_msgs REQUIRED)
109
find_package(ament_cmake REQUIRED)
11-
find_package(sensor_msgs REQUIRED)
12-
find_package(rclpy REQUIRED)
13-
find_package(tf2 REQUIRED)
10+
find_package(bio_ik_msgs REQUIRED)
11+
find_package(bitbots_docs REQUIRED)
1412
find_package(bitbots_msgs REQUIRED)
13+
find_package(geometry_msgs REQUIRED)
14+
find_package(rclpy REQUIRED)
15+
find_package(sensor_msgs REQUIRED)
1516
find_package(std_msgs REQUIRED)
16-
find_package(tf2_geometry_msgs REQUIRED)
1717
find_package(std_srvs REQUIRED)
18-
find_package(geometry_msgs REQUIRED)
19-
find_package(bitbots_docs REQUIRED)
18+
find_package(tf2 REQUIRED)
19+
find_package(tf2_geometry_msgs REQUIRED)
2020

2121
set(INCLUDE_DIRS
2222
${bio_ik_msgs_INCLUDE_DIRS}
@@ -76,6 +76,11 @@ ament_export_dependencies(geometry_msgs)
7676
ament_export_dependencies(bitbots_docs)
7777
ament_export_include_directories(${INCLUDE_DIRS})
7878

79+
if(BUILD_TESTING)
80+
find_package(ament_cmake_mypy REQUIRED)
81+
ament_mypy(CONFIG_FILE "${CMAKE_CURRENT_LIST_DIR}/mypy.ini")
82+
endif()
83+
7984
ament_python_install_package(${PROJECT_NAME})
8085

8186
ament_package()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Setting up runtime type checking for this package
2+
from beartype.claw import beartype_this_package
3+
4+
beartype_this_package()

bitbots_behavior/bitbots_blackboard/bitbots_blackboard/body_blackboard.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414

1515
class BodyBlackboard:
16-
def __init__(self, node: Node, tf_buffer: tf2.Buffer):
16+
def __init__(self, node: Node, tf_buffer: tf2.BufferInterface):
1717
# References
1818
self.node = node
1919
self.tf_buffer = tf_buffer
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
from typing import TYPE_CHECKING, Optional
1+
from typing import TYPE_CHECKING
22

3+
from bitbots_utils.utils import nobeartype
34
from rclpy.node import Node
45

56
if TYPE_CHECKING:
@@ -9,6 +10,7 @@
910
class AbstractBlackboardCapsule:
1011
"""Abstract class for blackboard capsules."""
1112

12-
def __init__(self, node: Node, blackboard: Optional["BodyBlackboard"] = None):
13+
@nobeartype
14+
def __init__(self, node: Node, blackboard: "BodyBlackboard"):
1315
self._node = node
1416
self._blackboard = blackboard

bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/costmap_capsule.py

+19-11
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def __init__(self, node, blackboard):
5353
self.calc_base_costmap()
5454
self.calc_gradients()
5555

56-
def robot_callback(self, msg: RobotArray):
56+
def robot_callback(self, msg: RobotArray) -> None:
5757
"""
5858
Callback with new robot detections
5959
"""
@@ -77,10 +77,11 @@ def robot_callback(self, msg: RobotArray):
7777
# Publish debug costmap
7878
self.publish_costmap()
7979

80-
def publish_costmap(self):
80+
def publish_costmap(self) -> None:
8181
"""
8282
Publishes the costmap for rviz
8383
"""
84+
assert self.costmap is not None, "Costmap is not initialized"
8485
# Normalize costmap to match the rviz color scheme in a good way
8586
normalized_costmap = (
8687
(255 - ((self.costmap - np.min(self.costmap)) / (np.max(self.costmap) - np.min(self.costmap))) * 255 / 2.1)
@@ -131,7 +132,7 @@ def get_pass_regions(self) -> np.ndarray:
131132
# Smooth obstacle map
132133
return gaussian_filter(costmap, pass_smooth)
133134

134-
def field_2_costmap_coord(self, x: float, y: float) -> Tuple[float, float]:
135+
def field_2_costmap_coord(self, x: float, y: float) -> Tuple[int, int]:
135136
"""
136137
Converts a field position to the corresponding indices for the costmap.
137138
@@ -153,10 +154,11 @@ def field_2_costmap_coord(self, x: float, y: float) -> Tuple[float, float]:
153154
)
154155
return idx_x, idx_y
155156

156-
def calc_gradients(self):
157+
def calc_gradients(self) -> None:
157158
"""
158159
Recalculates the gradient map based on the current costmap.
159160
"""
161+
assert self.base_costmap is not None, "Base costmap is not initialized"
160162
gradient = np.gradient(self.base_costmap)
161163
norms = np.linalg.norm(gradient, axis=0)
162164

@@ -186,7 +188,7 @@ def cost_at_relative_xy(self, x: float, y: float) -> float:
186188

187189
return self.get_cost_at_field_position(point.point.x, point.point.y)
188190

189-
def calc_base_costmap(self):
191+
def calc_base_costmap(self) -> None:
190192
"""
191193
Builds the base costmap based on the behavior parameters.
192194
This costmap includes a gradient towards the enemy goal and high costs outside the playable area
@@ -203,8 +205,8 @@ def calc_base_costmap(self):
203205

204206
# Create Grid
205207
grid_x, grid_y = np.mgrid[
206-
0 : self.field_length + self.map_margin * 2 : (self.field_length + self.map_margin * 2) * 10j,
207-
0 : self.field_width + self.map_margin * 2 : (self.field_width + self.map_margin * 2) * 10j,
208+
0 : self.field_length + self.map_margin * 2 : (self.field_length + self.map_margin * 2) * 10j, # type: ignore[misc]
209+
0 : self.field_width + self.map_margin * 2 : (self.field_width + self.map_margin * 2) * 10j, # type: ignore[misc]
208210
]
209211

210212
fix_points: List[Tuple[Tuple[float, float], float]] = []
@@ -278,7 +280,8 @@ def calc_base_costmap(self):
278280
)
279281

280282
# Smooth the costmap to get more continuous gradients
281-
self.base_costmap = gaussian_filter(interpolated, self.body_config["base_costmap_smoothing_sigma"])
283+
base_costmap: np.ndarray = gaussian_filter(interpolated, self.body_config["base_costmap_smoothing_sigma"])
284+
self.base_costmap = base_costmap
282285
self.costmap = self.base_costmap.copy()
283286

284287
def get_gradient_at_field_position(self, x: float, y: float) -> Tuple[float, float]:
@@ -287,6 +290,7 @@ def get_gradient_at_field_position(self, x: float, y: float) -> Tuple[float, flo
287290
:param x: Field coordinate in the x direction
288291
:param y: Field coordinate in the y direction
289292
"""
293+
assert self.gradient_map is not None, "Gradient map is not initialized"
290294
idx_x, idx_y = self.field_2_costmap_coord(x, y)
291295
return -self.gradient_map[0][idx_x, idx_y], -self.gradient_map[1][idx_x, idx_y]
292296

@@ -296,10 +300,11 @@ def get_cost_at_field_position(self, x: float, y: float) -> float:
296300
:param x: Field coordinate in the x direction
297301
:param y: Field coordinate in the y direction
298302
"""
303+
assert self.costmap is not None, "Costmap is not initialized"
299304
idx_x, idx_y = self.field_2_costmap_coord(x, y)
300305
return self.costmap[idx_x, idx_y]
301306

302-
def get_gradient_direction_at_field_position(self, x: float, y: float):
307+
def get_gradient_direction_at_field_position(self, x: float, y: float) -> float:
303308
"""
304309
Returns the gradient direction at the given position
305310
:param x: Field coordinate in the x direction
@@ -318,7 +323,9 @@ def get_gradient_direction_at_field_position(self, x: float, y: float):
318323
grad = self.get_gradient_at_field_position(x, y)
319324
return math.atan2(grad[1], grad[0])
320325

321-
def get_cost_of_kick_relative(self, x: float, y: float, direction: float, kick_length: float, angular_range: float):
326+
def get_cost_of_kick_relative(
327+
self, x: float, y: float, direction: float, kick_length: float, angular_range: float
328+
) -> float:
322329
"""
323330
Returns the cost of a kick at the given position and direction in base footprint frame
324331
:param x: Field coordinate in the x direction
@@ -356,6 +363,7 @@ def get_cost_of_kick(self, x: float, y: float, direction: float, kick_length: fl
356363
:param kick_length: The length of the kick
357364
:param angular_range: The angular range of the kick
358365
"""
366+
assert self.costmap is not None, "Costmap is not initialized"
359367

360368
# create a mask in the size of the costmap consisting of 8-bit values initialized as 0
361369
mask = Image.new("L", (self.costmap.shape[1], self.costmap.shape[0]))
@@ -386,7 +394,7 @@ def get_cost_of_kick(self, x: float, y: float, direction: float, kick_length: fl
386394
# This should contribute way less than the max and should have an impact if the max values are similar in all directions.
387395
return masked_costmap.max() * 0.75 + masked_costmap.min() * 0.25
388396

389-
def get_current_cost_of_kick(self, direction: float, kick_length: float, angular_range: float):
397+
def get_current_cost_of_kick(self, direction: float, kick_length: float, angular_range: float) -> float:
390398
"""
391399
Returns the cost of the kick at the current position
392400
:param direction: The direction of the kick

bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/game_status_capsule.py

+31-39
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Optional
2+
13
from bitbots_utils.utils import get_parameters_from_other_node
24
from game_controller_hl_interfaces.msg import GameState
35

@@ -9,96 +11,86 @@ class GameStatusCapsule(AbstractBlackboardCapsule):
911

1012
def __init__(self, node, blackboard=None):
1113
super().__init__(node, blackboard)
12-
self.team_id = get_parameters_from_other_node(self._node, "parameter_blackboard", ["team_id"])["team_id"]
14+
self.team_id: int = get_parameters_from_other_node(self._node, "parameter_blackboard", ["team_id"])["team_id"]
1315
self.gamestate = GameState()
14-
self.last_update = 0
15-
self.unpenalized_time = 0
16-
self.last_goal_from_us_time = -86400
17-
self.last_goal_time = -86400
18-
self.free_kick_kickoff_team = None
19-
20-
def is_game_state_equals(self, value):
21-
assert value in [
22-
GameState.GAMESTATE_PLAYING,
23-
GameState.GAMESTATE_FINISHED,
24-
GameState.GAMESTATE_INITIAL,
25-
GameState.GAMESTATE_READY,
26-
GameState.GAMESTATE_SET,
27-
]
28-
return value == self.get_gamestate()
29-
30-
def get_gamestate(self):
16+
self.last_update: float = 0.0
17+
self.unpenalized_time: float = 0.0
18+
self.last_goal_from_us_time = -86400.0
19+
self.last_goal_time = -86400.0
20+
self.free_kick_kickoff_team: Optional[bool] = None
21+
22+
def get_gamestate(self) -> int:
3123
return self.gamestate.game_state
3224

33-
def get_secondary_state(self):
25+
def get_secondary_state(self) -> int:
3426
return self.gamestate.secondary_state
3527

36-
def get_secondary_state_mode(self):
28+
def get_secondary_state_mode(self) -> int:
3729
return self.gamestate.secondary_state_mode
3830

39-
def get_secondary_team(self):
31+
def get_secondary_team(self) -> int:
4032
return self.gamestate.secondary_state_team
4133

42-
def has_kickoff(self):
34+
def has_kickoff(self) -> bool:
4335
return self.gamestate.has_kick_off
4436

45-
def has_penalty_kick(self):
37+
def has_penalty_kick(self) -> bool:
4638
return (
4739
self.gamestate.secondary_state == GameState.STATE_PENALTYKICK
4840
or self.gamestate.secondary_state == GameState.STATE_PENALTYSHOOT
4941
) and self.gamestate._secondary_state_team == self.team_id
5042

51-
def get_own_goals(self):
43+
def get_our_goals(self) -> int:
5244
return self.gamestate.own_score
5345

54-
def get_opp_goals(self):
46+
def get_opp_goals(self) -> int:
5547
return self.gamestate.rival_score
5648

57-
def get_seconds_since_own_goal(self):
49+
def get_seconds_since_own_goal(self) -> float:
5850
return self._node.get_clock().now().nanoseconds / 1e9 - self.last_goal_from_us_time
5951

60-
def get_seconds_since_any_goal(self):
52+
def get_seconds_since_any_goal(self) -> float:
6153
return self._node.get_clock().now().nanoseconds / 1e9 - self.last_goal_time
6254

63-
def get_seconds_remaining(self):
55+
def get_seconds_remaining(self) -> float:
6456
# Time from the message minus time passed since receiving it
6557
return max(
66-
self.gamestate.seconds_remaining - (self._node.get_clock().now().nanoseconds / 1e9 - self.last_update), 0
58+
self.gamestate.seconds_remaining - (self._node.get_clock().now().nanoseconds / 1e9 - self.last_update), 0.0
6759
)
6860

69-
def get_secondary_seconds_remaining(self):
61+
def get_secondary_seconds_remaining(self) -> float:
7062
"""Seconds remaining for things like kickoff"""
7163
# Time from the message minus time passed since receiving it
7264
return max(
7365
self.gamestate.secondary_seconds_remaining
7466
- (self._node.get_clock().now().nanoseconds / 1e9 - self.last_update),
75-
0,
67+
0.0,
7668
)
7769

78-
def get_seconds_since_last_drop_ball(self):
70+
def get_seconds_since_last_drop_ball(self) -> Optional[float]:
7971
"""Returns the seconds since the last drop in"""
8072
if self.gamestate.drop_in_time == -1:
8173
return None
8274
else:
8375
# Time from the message plus seconds passed since receiving it
8476
return self.gamestate.drop_in_time + (self._node.get_clock().now().nanoseconds / 1e9 - self.last_update)
8577

86-
def get_seconds_since_unpenalized(self):
78+
def get_seconds_since_unpenalized(self) -> float:
8779
return self._node.get_clock().now().nanoseconds / 1e9 - self.unpenalized_time
8880

89-
def get_is_penalized(self):
81+
def get_is_penalized(self) -> bool:
9082
return self.gamestate.penalized
9183

92-
def received_gamestate(self):
93-
return self.last_update != 0
84+
def received_gamestate(self) -> bool:
85+
return self.last_update != 0.0
9486

95-
def get_team_id(self):
87+
def get_team_id(self) -> int:
9688
return self.team_id
9789

98-
def get_red_cards(self):
90+
def get_red_cards(self) -> int:
9991
return self.gamestate.team_mates_with_red_card
10092

101-
def gamestate_callback(self, gamestate_msg: GameState):
93+
def gamestate_callback(self, gamestate_msg: GameState) -> None:
10294
if self.gamestate.penalized and not gamestate_msg.penalized:
10395
self.unpenalized_time = self._node.get_clock().now().nanoseconds / 1e9
10496

bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/kick_capsule.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class KickCapsule(AbstractBlackboardCapsule):
2323
is_currently_kicking: bool = False
2424

2525
__connected: bool = False
26-
__action_client: ActionClient = None
26+
__action_client: Optional[ActionClient] = None
2727

2828
class WalkKickTargets(Flag):
2929
"""
@@ -43,22 +43,22 @@ def __init__(self, node, blackboard):
4343
self.walk_kick_pub = self._node.create_publisher(Bool, "/kick", 1)
4444
# self.connect_dynamic_kick() Do not connect if dynamic_kick is disabled
4545

46-
def walk_kick(self, target: WalkKickTargets):
46+
def walk_kick(self, target: WalkKickTargets) -> None:
4747
"""
4848
Kick the ball while walking
4949
:param target: Target for the walk kick (e.g. left or right foot)
5050
"""
5151
self.walk_kick_pub.publish(Bool(data=target.value))
5252

53-
def connect_dynamic_kick(self):
53+
def connect_dynamic_kick(self) -> None:
5454
topic = self._blackboard.config["dynamic_kick"]["topic"]
5555
self.__action_client = ActionClient(self._node, Kick, topic, callback_group=ReentrantCallbackGroup())
5656
self.__connected = self.__action_client.wait_for_server(self._blackboard.config["dynamic_kick"]["wait_time"])
5757

5858
if not self.__connected:
5959
self._node.get_logger().error(f"No dynamic_kick server running on {topic}")
6060

61-
def dynamic_kick(self, goal: Kick.Goal):
61+
def dynamic_kick(self, goal: Kick.Goal) -> None:
6262
"""
6363
:param goal: Goal to kick to
6464
:type goal: KickGoal

0 commit comments

Comments
 (0)