Skip to content

Commit 76edbe1

Browse files
author
Cornelius Krupp
committed
Add proper recording file controls, correct teleportation behavior
1 parent 4341641 commit 76edbe1

6 files changed

Lines changed: 67 additions & 27 deletions

File tree

src/bitbots_misc/bitbots_auto_test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ project(bitbots_auto_test)
44
find_package(ament_cmake REQUIRED)
55
find_package(bitbots_docs REQUIRED)
66
find_package(bitbots_msgs REQUIRED)
7+
find_package(game_controller_hsl REQUIRED)
78
find_package(rclpy REQUIRED)
89

910
enable_bitbots_docs()

src/bitbots_misc/bitbots_auto_test/bitbots_auto_test/auto_test.py

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,10 @@
1515
from rclpy.node import Node
1616
from rclpy.parameter import Parameter
1717
from rclpy.qos import DurabilityPolicy, QoSProfile
18-
from std_srvs.srv import Empty
19-
18+
from tf_transformations import euler_from_quaternion, quaternion_from_euler
2019

2120
from bitbots_auto_test.monitoring_node import Monitoring
22-
from bitbots_msgs.srv import SetObjectPose, SetObjectPosition
21+
from bitbots_msgs.srv import SetObjectPose, SetObjectPosition, SimulatorRecord
2322

2423

2524
class TestResult:
@@ -58,7 +57,7 @@ def start_recording(self):
5857
self.start_time = self.handle.get_clock().now()
5958

6059
def judge(self) -> float:
61-
pass
60+
raise NotImplementedError(f"{self.__class__.__name__}.judge")
6261

6362
def is_over(self) -> TestResult | None:
6463
if not self.is_pose_in(self.handle.robot_pose, self.FIELD_BOUNDS):
@@ -82,23 +81,33 @@ def score(self):
8281
else:
8382
return self.judge()
8483

85-
def set_robot(self, x: float, y: float):
84+
def set_robot(self, x: float, y: float, angle: float = 0.0):
8685
request = SetObjectPose.Request()
8786
request.object_name = "amy"
8887
request.pose.position.x = x
8988
request.pose.position.y = y
9089
request.pose.position.z = 0.42
91-
request.pose.orientation.x = 0.0
92-
request.pose.orientation.y = 0.0
93-
request.pose.orientation.z = 0.0
94-
request.pose.orientation.w = 0.0
90+
91+
old_orientation = self.handle.robot_pose.orientation
92+
old_quat = [old_orientation.x, old_orientation.y, old_orientation.z, old_orientation.w]
93+
try:
94+
old_roll, old_pitch, _old_yaw = euler_from_quaternion(old_quat)
95+
except ValueError:
96+
old_roll, old_pitch, _old_yaw = 0.0, 0.0, 0.0
97+
98+
x_q, y_q, z_q, w_q = quaternion_from_euler(old_roll, old_pitch, angle)
99+
request.pose.orientation.x = x_q
100+
request.pose.orientation.y = y_q
101+
request.pose.orientation.z = z_q
102+
request.pose.orientation.w = w_q
103+
95104
self.handle.set_robot_pose_service.call(request)
96105
if not self.handle.get_parameter("fake_localization").value:
97106
rf = ResetFilter.Request()
98107
rf.init_mode = ResetFilter.Request.POSE
99108
rf.x = x
100109
rf.y = y
101-
rf.angle = 0.0
110+
rf.angle = angle
102111
self.handle.reset_localization.call(rf)
103112

104113
def set_ball(self, x: float, y: float):
@@ -222,7 +231,7 @@ def __init__(self, monitoring_node: Monitoring):
222231

223232
self.set_robot_pose_service = self.create_client(SetObjectPose, "set_robot_pose")
224233
self.set_ball_pos_service = self.create_client(SetObjectPosition, "set_ball_position")
225-
self.recording_service = self.create_client(Empty, "simulator_record")
234+
self.recording_service = self.create_client(SimulatorRecord, "simulator_record")
226235
self.create_subscription(ModelStates, "/model_states", qos_profile=10, callback=self.model_states_callback)
227236
self.ball_pose = Pose()
228237
self.ball_twist = Twist()
@@ -274,15 +283,17 @@ def setup_sequence(self):
274283
gs_msg.header.stamp = self.get_clock().now().to_msg()
275284
gs_msg.kicking_team = TEAM_ID
276285
gs_msg.main_state = 2 # 2=SET, force robot to stand still
286+
gs_msg.stopped = True
277287
self.gamestate_publisher.publish(gs_msg)
278288

279-
self.get_clock().sleep_for(Duration(seconds=2)) # Let robot react & simulation settle
289+
self.get_clock().sleep_for(Duration(seconds=3)) # Let robot react & simulation settle
280290
self.current_test.setup() # Teleport robot
281291

282-
self.get_clock().sleep_for(Duration(seconds=2)) # Let simulation settle
292+
self.get_clock().sleep_for(Duration(seconds=3)) # Let simulation settle
283293

284294
gs_msg.header.stamp = self.get_clock().now().to_msg()
285295
gs_msg.main_state = 3 # 3=PLAYING, make robot play ball
296+
gs_msg.stopped = False
286297
self.gamestate_publisher.publish(gs_msg)
287298
self.current_test.start_recording()
288299
self.monitoring_node.write_event(
@@ -313,12 +324,13 @@ def finished(self):
313324
)
314325

315326
def loop(self):
316-
start = self.get_clock().now()
317-
while self.get_clock().now() - start < Duration(seconds=1):
318-
pass
327+
for service in [self.set_robot_pose_service, self.set_ball_pos_service, self.recording_service]:
328+
service.wait_for_service()
329+
self.get_clock().sleep_for(Duration(seconds=1))
319330
self.get_logger().info("Starting main loop")
320-
request = Empty.Request()
321-
self.recording_service.call(request) # start
331+
request = SimulatorRecord.Request()
332+
request.start = True
333+
self.recording_service.call(request) # start
322334
self.next_test()
323335
self.setup_sequence()
324336
while True:
@@ -331,7 +343,9 @@ def loop(self):
331343
self.get_logger().info("Done with all tests")
332344
break
333345
self.setup_sequence()
334-
self.recording_service.call(request) # stop
346+
stop_request = SimulatorRecord.Request()
347+
stop_request.stop = True
348+
self.recording_service.call(stop_request) # stop
335349

336350

337351
def main():

src/bitbots_misc/bitbots_auto_test/package.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<depend>rclpy</depend>
2020
<depend>std_msgs</depend>
2121
<depend>tf_transformations</depend>
22-
<depend>game_controller_hl</depend>
22+
<depend>game_controller_hsl</depend>
2323

2424
<test_depend>python3-pytest</test_depend>
2525

src/bitbots_msgs/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ rosidl_generate_interfaces(
5656
"srv/SetObjectPosition.srv"
5757
"srv/SetTeachingMode.srv"
5858
"srv/SimulatorPush.srv"
59+
"srv/SimulatorRecord.srv"
5960
DEPENDENCIES
6061
action_msgs
6162
geometry_msgs
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
bool start False
2+
bool stop False
3+
string file_path ""
4+
---
5+
bool running
6+
string file_path

src/bitbots_simulation/bitbots_webots_sim/bitbots_webots_sim/webots_supervisor_controller.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from rosgraph_msgs.msg import Clock
1212
from std_srvs.srv import Empty
1313

14-
from bitbots_msgs.srv import SetObjectPose, SetObjectPosition, SimulatorPush
14+
from bitbots_msgs.srv import SetObjectPose, SetObjectPosition, SimulatorPush, SimulatorRecord
1515

1616
G = 9.81
1717

@@ -64,6 +64,7 @@ def __init__(
6464
self.joint_nodes = {}
6565
self.link_nodes = {}
6666
self.currently_recording = False
67+
self.current_recording_file_path = ""
6768

6869
# set reset height based on the robot, so that we can reset different robots easily
6970
self.robot_type = robot
@@ -125,7 +126,7 @@ def __init__(
125126
SimulatorPush, base_ns + "simulator_push", self.simulator_push
126127
)
127128
self.simulator_record_service = self.ros_node.create_service(
128-
Empty, base_ns + "simulator_record", self.simulator_record
129+
SimulatorRecord, base_ns + "simulator_record", self.simulator_record
129130
)
130131

131132
self.world_info = self.supervisor.getFromDef("world_info")
@@ -255,14 +256,31 @@ def simulator_push(self, request=None, response=None):
255256
return response or Empty.Response()
256257

257258
def simulator_record(self, request=None, response=None):
258-
if not self.currently_recording:
259-
file_path = str((Path.home() / "latest_run" /"latest_run.html").absolute())
259+
if request is None:
260+
request = SimulatorRecord.Request()
261+
262+
if request.start:
263+
file_path = (
264+
request.file_path
265+
if request.file_path
266+
else str((Path.home() / "latest_run" / "latest_run.html").absolute())
267+
)
260268
self.ros_node.get_logger().info(f"Starting recording to file {file_path}")
261269
self.currently_recording = self.supervisor.animationStartRecording(file_path)
270+
self.current_recording_file_path = file_path
271+
elif request.stop:
272+
if self.currently_recording:
273+
self.ros_node.get_logger().info("Stopping recording")
274+
self.currently_recording = not self.supervisor.animationStopRecording()
275+
else:
276+
self.ros_node.get_logger().info("Recording is not active")
262277
else:
263-
self.ros_node.get_logger().info("Stopping recording")
264-
self.currently_recording = not self.supervisor.animationStopRecording()
265-
return response or Empty.Response()
278+
self.ros_node.get_logger().info("Querying recording state")
279+
280+
response = response or SimulatorRecord.Response()
281+
response.running = self.currently_recording
282+
response.file_path = self.current_recording_file_path
283+
return response
266284

267285
def on_shutdown(self):
268286
if self.currently_recording:

0 commit comments

Comments
 (0)