Skip to content

Commit 9c2695e

Browse files
authored
Merge branch 'main' into feature/non-root-dev-container
2 parents 5818cea + c41da4b commit 9c2695e

File tree

104 files changed

+2716
-2232
lines changed

Some content is hidden

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

104 files changed

+2716
-2232
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -225,3 +225,5 @@ doku/*
225225
# Protobuf generated file
226226
/bitbots_team_communication/bitbots_team_communication/bitbots_team_communication/robocup_extension_pb2.py
227227

228+
# Workspace git status file from the deploy tool
229+
**/workspace_status.json

.vscode/settings.json

+10-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"cSpell.words": [
66
"animatable",
77
"ansible",
8+
"antiwindup",
89
"autoconnect",
910
"basler",
1011
"bitbot",
@@ -14,6 +15,7 @@
1415
"chrt",
1516
"coef",
1617
"colcon",
18+
"conv",
1719
"cornerkick",
1820
"costmap",
1921
"costmaps",
@@ -52,6 +54,7 @@
5254
"joern",
5355
"jupyter",
5456
"kalman",
57+
"Leph",
5558
"linalg",
5659
"matplotlib",
5760
"mmse",
@@ -77,6 +80,8 @@
7780
"proto",
7881
"protos",
7982
"pyplot",
83+
"pywrapper",
84+
"Quaterniond",
8085
"rclcpp",
8186
"rclpy",
8287
"reapproach",
@@ -90,6 +95,7 @@
9095
"rosgraph",
9196
"rosout",
9297
"roundrobin",
98+
"Rouxel",
9399
"rtype",
94100
"rviz",
95101
"scipy",
@@ -113,12 +119,13 @@
113119
"walkready",
114120
"wandb",
115121
"webots",
122+
"Werror",
116123
"wifi",
117124
"wolfgang",
118125
"xacro",
119126
"yoeo",
120127
"yolo",
121-
"yolov",
128+
"yolov"
122129
],
123130
"files.autoSave": "afterDelay",
124131
"files.autoSaveDelay": 500,
@@ -204,7 +211,8 @@
204211
"valarray": "cpp",
205212
"variant": "cpp",
206213
"regex": "cpp",
207-
"future": "cpp"
214+
"future": "cpp",
215+
"*.ipp": "cpp"
208216
},
209217
// Tell the ROS extension where to find the setup.bash
210218
// This also utilizes the COLCON_WS environment variable, which needs to be set

bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/kick_capsule.py

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ def __init__(self, node, blackboard):
3030
self.connect()
3131

3232
def connect(self):
33+
# Do not connect if dynamic_kick is disabled
34+
return
3335
topic = self._blackboard.config["dynamic_kick"]["topic"]
3436
self.__action_client = ActionClient(self._node, Kick, topic, callback_group=ReentrantCallbackGroup())
3537
self.__connected = self.__action_client.wait_for_server(self._blackboard.config["dynamic_kick"]["wait_time"])
@@ -43,6 +45,7 @@ def kick(self, goal: Kick.Goal):
4345
:type goal: KickGoal
4446
:raises RuntimeError: when not connected to dynamic_kick server
4547
"""
48+
raise NotImplementedError("The dynamic_kick is disabled currently")
4649
if not self.__connected:
4750
# try to connect again
4851
self.__connected = self.__action_client.wait_for_server(

bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/team_data_capsule.py

-10
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import numpy as np
44
from bitbots_utils.utils import get_parameters_from_other_node
55
from geometry_msgs.msg import PointStamped, Pose
6-
from rclpy.clock import ClockType
76
from rclpy.duration import Duration
87
from rclpy.time import Time
98
from ros2_numpy import numpify
@@ -68,7 +67,6 @@ def __init__(self, node, blackboard):
6867
# Config
6968
self.data_timeout: float = self._node.get_parameter("team_data_timeout").value
7069
self.ball_max_covariance: float = self._node.get_parameter("ball_max_covariance").value
71-
self.ball_lost_time: float = Duration(seconds=self._node.get_parameter("ball_lost_time").value)
7270

7371
def is_valid(self, data: TeamData) -> bool:
7472
"""
@@ -205,14 +203,6 @@ def publish_strategy(self):
205203
def publish_time_to_ball(self):
206204
self.time_to_ball_publisher.publish(Float32(data=self.own_time_to_ball))
207205

208-
def get_teammate_ball_seen_time(self) -> Time:
209-
"""Returns the time at which a teammate has seen the ball accurately enough"""
210-
teammate_ball = self.get_teammate_ball()
211-
if teammate_ball is not None:
212-
return Time.from_msg(teammate_ball.header.stamp)
213-
else:
214-
return Time(clock_type=ClockType.ROS_TIME)
215-
216206
def teammate_ball_is_valid(self):
217207
"""Returns true if a teammate has seen the ball accurately enough"""
218208
return self.get_teammate_ball() is not None

bitbots_behavior/bitbots_blackboard/bitbots_blackboard/capsules/world_model_capsule.py

+51-71
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import tf2_ros as tf2
66
from bitbots_utils.utils import get_parameters_from_other_node
77
from geometry_msgs.msg import (
8+
Pose,
89
PoseStamped,
910
PoseWithCovarianceStamped,
1011
TransformStamped,
@@ -49,15 +50,14 @@ def __init__(self, node, blackboard):
4950
self.map_frame: str = self._node.get_parameter("map_frame").value
5051

5152
# Get body parameters
52-
self.ball_lost_time = Duration(seconds=self._node.get_parameter("ball_lost_time").value)
5353
self.ball_max_covariance = self._node.get_parameter("ball_max_covariance").value
5454
self.map_margin: float = self._node.get_parameter("map_margin").value
5555

5656
# Ball state
57-
self._ball_seen_time: Time = Time(clock_type=ClockType.ROS_TIME) # Time when the ball was last seen
5857
self._ball: PointStamped = PointStamped(
5958
header=Header(stamp=Time(clock_type=ClockType.ROS_TIME), frame_id=self.map_frame)
6059
) # The ball in the map frame (default to the center of the field if ball is not seen yet)
60+
self._ball_covariance: np.ndarray = np.zeros((2, 2)) # Covariance of the ball
6161

6262
# Publisher for visualization in RViZ
6363
self.debug_publisher_used_ball = self._node.create_publisher(PointStamped, "debug/behavior/used_ball", 1)
@@ -71,22 +71,12 @@ def __init__(self, node, blackboard):
7171
############
7272

7373
def ball_seen_self(self) -> bool:
74-
"""Returns true if we have seen the ball recently (less than ball_lost_time ago)"""
75-
return self._node.get_clock().now() - self._ball_seen_time < self.ball_lost_time
76-
77-
def ball_last_seen(self) -> Time:
78-
"""
79-
Returns the time at which the ball was last seen if it is in the threshold or
80-
the more recent ball from either the teammate or itself if teamcomm is available
81-
"""
82-
if self.ball_seen_self():
83-
return self._ball_seen_time
84-
else:
85-
return max(self._ball_seen_time, self._blackboard.team_data.get_teammate_ball_seen_time())
74+
"""Returns true we are reasonably sure that we have seen the ball"""
75+
return all(np.diag(self._ball_covariance) < self.ball_max_covariance)
8676

8777
def ball_has_been_seen(self) -> bool:
88-
"""Returns true if we or a teammate have seen the ball recently (less than ball_lost_time ago)"""
89-
return self._node.get_clock().now() - self.ball_last_seen() < self.ball_lost_time
78+
"""Returns true if we or a teammate are reasonably sure that we have seen the ball"""
79+
return self.ball_seen_self() or self._blackboard.team_data.teammate_ball_is_valid()
9080

9181
def get_ball_position_xy(self) -> Tuple[float, float]:
9282
"""Return the ball saved in the map frame, meaning the absolute position of the ball on the field"""
@@ -108,16 +98,17 @@ def get_best_ball_point_stamped(self) -> PointStamped:
10898
self.debug_publisher_which_ball.publish(Header(stamp=teammate_ball.header.stamp, frame_id="teammate_ball"))
10999
return teammate_ball
110100

111-
# Otherwise, use the own ball even if it is too old
101+
# Otherwise, use the own ball even if it is bad
112102
if not self.ball_seen_self():
113-
self._node.get_logger().warn("Using own ball even though it is too old, as no teammate ball is available")
103+
self._node.get_logger().warn("Using own ball even though it is bad, as no teammate ball is available")
114104
self.debug_publisher_used_ball.publish(own_ball)
115105
self.debug_publisher_which_ball.publish(Header(stamp=own_ball.header.stamp, frame_id="own_ball_map"))
116106
return own_ball
117107

118108
def get_ball_position_uv(self) -> Tuple[float, float]:
119109
"""
120-
Returns the ball position relative to the robot in the base_footprint frame
110+
Returns the ball position relative to the robot in the base_footprint frame.
111+
U and V are returned, where positive U is forward, positive V is to the left.
121112
"""
122113
ball = self.get_best_ball_point_stamped()
123114
try:
@@ -131,58 +122,50 @@ def get_ball_position_uv(self) -> Tuple[float, float]:
131122
return ball_bfp.x, ball_bfp.y
132123

133124
def get_ball_distance(self) -> float:
134-
ball_pos = self.get_ball_position_uv()
135-
if ball_pos is None:
125+
"""
126+
Returns the distance to the ball in meters.
127+
"""
128+
if not (ball_pos := self.get_ball_position_uv()):
136129
return np.inf # worst case (very far away)
137-
else:
138-
u, v = ball_pos
139-
130+
u, v = ball_pos
140131
return math.hypot(u, v)
141132

142133
def get_ball_angle(self) -> float:
143-
ball_pos = self.get_ball_position_uv()
144-
if ball_pos is None:
134+
"""
135+
Returns the angle to the ball in radians.
136+
0 is straight ahead, positive is to the left, negative is to the right.
137+
"""
138+
if not (ball_pos := self.get_ball_position_uv()):
145139
return -math.pi # worst case (behind robot)
146-
else:
147-
u, v = ball_pos
140+
u, v = ball_pos
148141
return math.atan2(v, u)
149142

150143
def ball_filtered_callback(self, msg: PoseWithCovarianceStamped):
151-
# When the precision is not sufficient, the ball ages.
152-
x_sdev = msg.pose.covariance[0] # position 0,0 in a 6x6-matrix
153-
y_sdev = msg.pose.covariance[7] # position 1,1 in a 6x6-matrix
154-
if x_sdev > self.ball_max_covariance or y_sdev > self.ball_max_covariance:
155-
self.forget_ball(reset_ball_filter=False)
156-
return
157-
158-
try:
159-
self._ball = self._blackboard.tf_buffer.transform(
160-
PointStamped(header=msg.header, point=msg.pose.pose.position),
161-
self.map_frame,
162-
timeout=Duration(seconds=1.0),
163-
)
164-
# Set timestamps to zero to get the newest transform when this is transformed later
165-
self._ball_seen_time = Time.from_msg(msg.header.stamp)
166-
self._ball.header.stamp = Time(clock_type=ClockType.ROS_TIME).to_msg()
144+
"""
145+
Handles incoming ball messages
146+
"""
147+
assert msg.header.frame_id == self.map_frame, "Ball needs to be in the map frame"
148+
149+
# Save ball
150+
self._ball = PointStamped(
151+
header=Header(
152+
# Set timestamps to zero to get the newest transform when this is transformed later
153+
stamp=Time(clock_type=ClockType.ROS_TIME).to_msg(),
154+
frame_id=self.map_frame,
155+
),
156+
point=msg.pose.pose.position,
157+
)
167158

168-
except (tf2.ConnectivityException, tf2.LookupException, tf2.ExtrapolationException) as e:
169-
self._node.get_logger().warn(str(e))
159+
# Save covariance (only x and y parts)
160+
self._ball_covariance = msg.pose.covariance.reshape((6, 6))[:2, :2]
170161

171-
def forget_ball(self, reset_ball_filter: bool = True) -> None:
162+
def forget_ball(self) -> None:
172163
"""
173-
Forget that we saw a ball, optionally reset the ball filter
174-
:param reset_ball_filter: Reset the ball filter, defaults to True
175-
:type reset_ball_filter: bool, optional
164+
Forget that we saw a ball
176165
"""
177-
# Forget own ball
178-
self._ball_seen_time = Time(clock_type=ClockType.ROS_TIME)
179-
180-
if reset_ball_filter: # Reset the ball filter
181-
result: Trigger.Response = self.reset_ball_filter.call(Trigger.Request())
182-
if result.success:
183-
self._node.get_logger().debug(f"Received message from ball filter: '{result.message}'")
184-
else:
185-
self._node.get_logger().warn(f"Ball filter reset failed with: '{result.message}'")
166+
result: Trigger.Response = self.reset_ball_filter.call(Trigger.Request())
167+
if not result.success:
168+
self._node.get_logger().error(f"Ball filter reset failed with: '{result.message}'")
186169

187170
########
188171
# Goal #
@@ -233,8 +216,7 @@ def get_current_position(self) -> Optional[Tuple[float, float, float]]:
233216
0,0,0 is the center of the field looking in the direction of the opponent goal.
234217
:returns x,y,theta:
235218
"""
236-
transform = self.get_current_position_transform()
237-
if transform is None:
219+
if not (transform := self.get_current_position_transform()):
238220
return None
239221
theta = euler_from_quaternion(numpify(transform.transform.rotation))[2]
240222
return transform.transform.translation.x, transform.transform.translation.y, theta
@@ -243,17 +225,15 @@ def get_current_position_pose_stamped(self) -> Optional[PoseStamped]:
243225
"""
244226
Returns the current position as determined by the localization as a PoseStamped
245227
"""
246-
transform = self.get_current_position_transform()
247-
if transform is None:
228+
if not (transform := self.get_current_position_transform()):
248229
return None
249-
ps = PoseStamped()
250-
ps.header = transform.header
251-
ps.pose.position = msgify(Point, numpify(transform.transform.translation))
252-
ps.pose.orientation.x = transform.transform.rotation.x
253-
ps.pose.orientation.y = transform.transform.rotation.y
254-
ps.pose.orientation.z = transform.transform.rotation.z
255-
ps.pose.orientation.w = transform.transform.rotation.w
256-
return ps
230+
return PoseStamped(
231+
header=transform.header,
232+
pose=Pose(
233+
position=msgify(Point, numpify(transform.transform.translation)),
234+
orientation=transform.transform.rotation,
235+
),
236+
)
257237

258238
def get_current_position_transform(self) -> TransformStamped:
259239
"""

bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/actions/forget_ball.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ class ForgetBall(AbstractActionElement):
66
blackboard: BodyBlackboard
77

88
def perform(self, reevaluate=False):
9-
self.blackboard.world_model.forget_ball(reset_ball_filter=True)
9+
self.blackboard.world_model.forget_ball()
1010
self.pop()

bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/actions/get_walkready.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ class GetWalkready(AbstractActionElement):
1010

1111
def __init__(self, blackboard, dsd, parameters):
1212
super().__init__(blackboard, dsd, parameters)
13-
self.direction = "walkready"
1413
self.first_perform = True
1514
self.active = False
1615

@@ -59,7 +58,7 @@ def start_animation(self) -> bool:
5958

6059
# Dynup action server is running, we can start the walkready action
6160
goal = Dynup.Goal()
62-
goal.direction = self.direction
61+
goal.direction = Dynup.Goal.DIRECTION_WALKREADY
6362
self.active = True
6463
self.dynup_action_current_goal = self.blackboard.animation.dynup_action_client.send_goal_async(goal)
6564
self.dynup_action_current_goal.add_done_callback(

bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/actions/kick_ball.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class AbstractKickAction(AbstractActionElement):
99
blackboard: BodyBlackboard
1010

1111
def pop(self):
12-
self.blackboard.world_model.forget_ball(reset_ball_filter=True)
12+
self.blackboard.world_model.forget_ball()
1313
super().pop()
1414

1515

bitbots_behavior/bitbots_body_behavior/bitbots_body_behavior/behavior_dsd/decisions/ball_seen.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,10 @@ def __init__(self, blackboard, dsd, parameters):
1010

1111
def perform(self, reevaluate=False):
1212
"""
13-
Determines whether the ball was seen recently (as defined in config)
13+
Determines whether we are confident regarding the ball's position.
1414
:param reevaluate:
1515
:return:
1616
"""
17-
self.publish_debug_data(
18-
"Ball lost time", self.blackboard.node.get_clock().now() - self.blackboard.world_model.ball_last_seen()
19-
)
2017
if self.blackboard.world_model.ball_has_been_seen():
2118
return "YES"
2219
return "NO"

bitbots_behavior/bitbots_body_behavior/config/body_behavior.yaml

-4
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,6 @@ body_behavior:
4848
# Time to wait in ready state before moving to role position to give the localization time to converge.
4949
ready_wait_time: 4.0
5050

51-
# When the ball has not been seen for `ball_lost_time` seconds,
52-
# it is considered lost and will be searched
53-
ball_lost_time: 3.0
54-
5551
# The orientation threshold defining which range (in Degrees) is acceptable as aligned to the goal (in each direction)
5652
goal_alignment_orientation_threshold: 5.0
5753

bitbots_lowlevel/bitbots_ros_control/config/wolfgang.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ wolfgang_hardware_interface:
3737
auto_torque: true
3838

3939
set_ROM_RAM: true # set the following values on startup to all motors
40+
start_delay: 0.5 # delay after the motors are turned on until values are written, in seconds
4041

4142
ROM_RAM_DEFAULT:
4243
# all names of parameters have to be the same as on the dynamixel table (see dynamixel_workbench_toolbox/src/dynamixel_item.cpp )

bitbots_lowlevel/bitbots_ros_control/include/bitbots_ros_control/wolfgang_hardware_interface.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ class WolfgangHardwareInterface {
3333
std::vector<std::vector<bitbots_ros_control::HardwareInterface *>> interfaces_;
3434
DynamixelServoHardwareInterface servo_interface_;
3535
rclcpp::Publisher<bitbots_msgs::msg::Audio>::SharedPtr speak_pub_;
36+
std::optional<rclcpp::Time> motor_start_time_;
37+
bool motor_first_write_{false};
3638

3739
// prevent unnecessary error when power is turned on
3840
bool first_ping_error_;

0 commit comments

Comments
 (0)