feat:完善仿真环境
This commit is contained in:
@@ -6,6 +6,14 @@ import time
|
|||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
# TF 发布相关导入(用于发布 base_link 和传感器静态 TF)
|
||||||
|
try:
|
||||||
|
from geometry_msgs.msg import TransformStamped
|
||||||
|
from tf2_ros import TransformBroadcaster, StaticTransformBroadcaster
|
||||||
|
TF_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
TF_AVAILABLE = False
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
parser = argparse.ArgumentParser(description="Isaac 标定车间构建脚本")
|
parser = argparse.ArgumentParser(description="Isaac 标定车间构建脚本")
|
||||||
@@ -100,6 +108,8 @@ def parse_args():
|
|||||||
parser.add_argument("--control-reference-speed-ms", type=float, default=0.3, help="运控遥测参考速度(m/s)")
|
parser.add_argument("--control-reference-speed-ms", type=float, default=0.3, help="运控遥测参考速度(m/s)")
|
||||||
parser.add_argument("--control-reference-y-m", type=float, default=0.0, help="运控直线参考轨迹的 Y 坐标(米)")
|
parser.add_argument("--control-reference-y-m", type=float, default=0.0, help="运控直线参考轨迹的 Y 坐标(米)")
|
||||||
parser.add_argument("--control-parameter-version", type=str, default="isaac_control_baseline_v1", help="当前控制参数版本")
|
parser.add_argument("--control-parameter-version", type=str, default="isaac_control_baseline_v1", help="当前控制参数版本")
|
||||||
|
parser.add_argument("--disable-vehicle-tf", action="store_true", help="不发布车辆 TF 变换")
|
||||||
|
parser.add_argument("--vehicle-tf-publish-hz", type=float, default=50.0, help="车辆 TF 发布频率(Hz)")
|
||||||
parser.add_argument("--disable-sensor-telemetry", action="store_true", help="不发布传感器标定遥测")
|
parser.add_argument("--disable-sensor-telemetry", action="store_true", help="不发布传感器标定遥测")
|
||||||
parser.add_argument("--sensor-telemetry-topic", type=str, default="/sensor_calibration/telemetry", help="传感器标定遥测话题")
|
parser.add_argument("--sensor-telemetry-topic", type=str, default="/sensor_calibration/telemetry", help="传感器标定遥测话题")
|
||||||
parser.add_argument("--sensor-telemetry-hz", type=float, default=10.0, help="传感器标定遥测发布频率(Hz)")
|
parser.add_argument("--sensor-telemetry-hz", type=float, default=10.0, help="传感器标定遥测发布频率(Hz)")
|
||||||
@@ -1144,6 +1154,181 @@ class ExternalTruthTelemetryPublisher:
|
|||||||
self.node = None
|
self.node = None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class VehicleTfPublisher:
|
||||||
|
"""发布车辆 base_link 动态 TF 和传感器静态 TF"""
|
||||||
|
|
||||||
|
def __init__(self, args):
|
||||||
|
self.enabled = TF_AVAILABLE and not getattr(args, 'disable_vehicle_tf', False)
|
||||||
|
self.tf_publish_hz = getattr(args, 'vehicle_tf_publish_hz', 50.0)
|
||||||
|
self.publish_period_sec = 0.0 if self.tf_publish_hz <= 0.0 else 1.0 / self.tf_publish_hz
|
||||||
|
self.last_publish_time = 0.0
|
||||||
|
self.node = None
|
||||||
|
self.tf_broadcaster = None
|
||||||
|
self.static_tf_broadcaster = None
|
||||||
|
self.static_tfs_published = False
|
||||||
|
|
||||||
|
# 传感器相对于 base_link 的安装位置(从 args 读取)
|
||||||
|
self.sensor_configs = {
|
||||||
|
'front_camera_link': {
|
||||||
|
'x': args.vehicle_camera_x,
|
||||||
|
'y': args.vehicle_camera_y,
|
||||||
|
'z': args.vehicle_camera_z,
|
||||||
|
'roll': 0.0,
|
||||||
|
'pitch': -math.pi / 2, # 相机朝下 -90度
|
||||||
|
'yaw': 0.0,
|
||||||
|
'enabled': not args.disable_vehicle_camera,
|
||||||
|
},
|
||||||
|
'down_camera_link': {
|
||||||
|
'x': args.down_camera_x,
|
||||||
|
'y': args.down_camera_y,
|
||||||
|
'z': args.down_camera_z,
|
||||||
|
'roll': 0.0,
|
||||||
|
'pitch': 0.0, # 朝下相机,垂直向下
|
||||||
|
'yaw': 0.0,
|
||||||
|
'enabled': not args.disable_down_camera,
|
||||||
|
},
|
||||||
|
'lidar_3d_link': {
|
||||||
|
'x': args.vehicle_lidar_x,
|
||||||
|
'y': args.vehicle_lidar_y,
|
||||||
|
'z': args.vehicle_lidar_z,
|
||||||
|
'roll': 0.0,
|
||||||
|
'pitch': 0.0,
|
||||||
|
'yaw': 0.0,
|
||||||
|
'enabled': not args.disable_vehicle_lidar,
|
||||||
|
},
|
||||||
|
'lidar_2d_link': {
|
||||||
|
'x': args.vehicle_2d_lidar_x,
|
||||||
|
'y': args.vehicle_2d_lidar_y,
|
||||||
|
'z': args.vehicle_2d_lidar_z,
|
||||||
|
'roll': 0.0,
|
||||||
|
'pitch': 0.0,
|
||||||
|
'yaw': 0.0,
|
||||||
|
'enabled': not args.disable_vehicle_2d_lidar,
|
||||||
|
},
|
||||||
|
'imu_link': {
|
||||||
|
'x': args.vehicle_imu_x,
|
||||||
|
'y': args.vehicle_imu_y,
|
||||||
|
'z': args.vehicle_imu_z,
|
||||||
|
'roll': 0.0,
|
||||||
|
'pitch': 0.0,
|
||||||
|
'yaw': 0.0,
|
||||||
|
'enabled': not args.disable_vehicle_imu,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if not self.enabled:
|
||||||
|
print("[*] 车辆 TF 发布已禁用(TF_AVAILABLE={})或参数禁用。".format(TF_AVAILABLE))
|
||||||
|
return
|
||||||
|
|
||||||
|
if rclpy is None:
|
||||||
|
print("[WARN] rclpy 不可用,车辆 TF 发布已禁用。")
|
||||||
|
self.enabled = False
|
||||||
|
return
|
||||||
|
|
||||||
|
if not rclpy.ok():
|
||||||
|
rclpy.init(args=None)
|
||||||
|
|
||||||
|
self.node = rclpy.create_node("isaac_vehicle_tf_publisher")
|
||||||
|
self.tf_broadcaster = TransformBroadcaster(self.node)
|
||||||
|
self.static_tf_broadcaster = StaticTransformBroadcaster(self.node)
|
||||||
|
print(f"[*] 车辆 TF 发布已启用: hz={self.tf_publish_hz}")
|
||||||
|
|
||||||
|
def _create_transform(self, frame_id, child_frame_id, x, y, z, roll, pitch, yaw, timestamp=None):
|
||||||
|
"""创建 TransformStamped 消息"""
|
||||||
|
t = TransformStamped()
|
||||||
|
if timestamp is None:
|
||||||
|
timestamp = self.node.get_clock().now().to_msg()
|
||||||
|
t.header.stamp = timestamp
|
||||||
|
t.header.frame_id = frame_id
|
||||||
|
t.child_frame_id = child_frame_id
|
||||||
|
t.transform.translation.x = x
|
||||||
|
t.transform.translation.y = y
|
||||||
|
t.transform.translation.z = z
|
||||||
|
|
||||||
|
# 欧拉角转四元数
|
||||||
|
cy = math.cos(yaw * 0.5)
|
||||||
|
sy = math.sin(yaw * 0.5)
|
||||||
|
cp = math.cos(pitch * 0.5)
|
||||||
|
sp = math.sin(pitch * 0.5)
|
||||||
|
cr = math.cos(roll * 0.5)
|
||||||
|
sr = math.sin(roll * 0.5)
|
||||||
|
|
||||||
|
t.transform.rotation.w = cr * cp * cy + sr * sp * sy
|
||||||
|
t.transform.rotation.x = sr * cp * cy - cr * sp * sy
|
||||||
|
t.transform.rotation.y = cr * sp * cy + sr * cp * sy
|
||||||
|
t.transform.rotation.z = cr * cp * sy - sr * sp * cy
|
||||||
|
|
||||||
|
return t
|
||||||
|
|
||||||
|
def publish_static_tfs(self):
|
||||||
|
"""发布传感器相对于 base_link 的静态 TF(只发布一次)"""
|
||||||
|
if not self.enabled or self.static_tfs_published:
|
||||||
|
return
|
||||||
|
|
||||||
|
transforms = []
|
||||||
|
timestamp = self.node.get_clock().now().to_msg()
|
||||||
|
|
||||||
|
for child_frame, config in self.sensor_configs.items():
|
||||||
|
if not config['enabled']:
|
||||||
|
continue
|
||||||
|
t = self._create_transform(
|
||||||
|
'base_link', child_frame,
|
||||||
|
config['x'], config['y'], config['z'],
|
||||||
|
config['roll'], config['pitch'], config['yaw'],
|
||||||
|
timestamp
|
||||||
|
)
|
||||||
|
transforms.append(t)
|
||||||
|
print(f"[*] 静态 TF: base_link -> {child_frame} ({config['x']:.3f}, {config['y']:.3f}, {config['z']:.3f})")
|
||||||
|
|
||||||
|
if transforms:
|
||||||
|
self.static_tf_broadcaster.sendTransform(transforms)
|
||||||
|
self.static_tfs_published = True
|
||||||
|
print(f"[*] 已发布 {len(transforms)} 个传感器静态 TF")
|
||||||
|
|
||||||
|
def publish(self, agv):
|
||||||
|
"""发布 base_link 相对于 world 的动态 TF"""
|
||||||
|
if not self.enabled:
|
||||||
|
return
|
||||||
|
|
||||||
|
now = time.time()
|
||||||
|
if self.publish_period_sec > 0.0 and (now - self.last_publish_time) < self.publish_period_sec:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 首先发布静态 TF(只发布一次)
|
||||||
|
if not self.static_tfs_published:
|
||||||
|
self.publish_static_tfs()
|
||||||
|
|
||||||
|
# 获取车辆世界位姿
|
||||||
|
position, quat = agv.get_world_pose()
|
||||||
|
|
||||||
|
# 四元数 (w, x, y, z) 转欧拉角(用于调试)
|
||||||
|
qw, qx, qy, qz = quat
|
||||||
|
yaw = math.atan2(2.0 * (qw * qz + qx * qy), 1.0 - 2.0 * (qy * qy + qz * qz))
|
||||||
|
|
||||||
|
# 发布 world -> base_link 动态 TF
|
||||||
|
timestamp = self.node.get_clock().now().to_msg()
|
||||||
|
t = self._create_transform(
|
||||||
|
'world', 'base_link',
|
||||||
|
position[0], position[1], position[2],
|
||||||
|
0.0, 0.0, yaw, # roll/pitch 设为 0,只保留 yaw
|
||||||
|
timestamp
|
||||||
|
)
|
||||||
|
# 使用原始四元数覆盖(更精确)
|
||||||
|
t.transform.rotation.w = qw
|
||||||
|
t.transform.rotation.x = qx
|
||||||
|
t.transform.rotation.y = qy
|
||||||
|
t.transform.rotation.z = qz
|
||||||
|
|
||||||
|
self.tf_broadcaster.sendTransform(t)
|
||||||
|
self.last_publish_time = now
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
if self.node is not None:
|
||||||
|
self.node.destroy_node()
|
||||||
|
self.node = None
|
||||||
|
|
||||||
class VehicleImuTopicPublisher:
|
class VehicleImuTopicPublisher:
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
self.enabled = not args.disable_vehicle_imu
|
self.enabled = not args.disable_vehicle_imu
|
||||||
@@ -2325,6 +2510,7 @@ class IsaacWorkshopRuntime:
|
|||||||
def main():
|
def main():
|
||||||
runtime = IsaacWorkshopRuntime(ARGS)
|
runtime = IsaacWorkshopRuntime(ARGS)
|
||||||
telemetry_publisher = ExternalTruthTelemetryPublisher(ARGS)
|
telemetry_publisher = ExternalTruthTelemetryPublisher(ARGS)
|
||||||
|
vehicle_tf_publisher = VehicleTfPublisher(ARGS)
|
||||||
vehicle_imu_publisher = VehicleImuTopicPublisher(ARGS)
|
vehicle_imu_publisher = VehicleImuTopicPublisher(ARGS)
|
||||||
chassis_telemetry_publisher = ChassisCalibrationTelemetryPublisher(ARGS)
|
chassis_telemetry_publisher = ChassisCalibrationTelemetryPublisher(ARGS)
|
||||||
control_telemetry_publisher = ControlCalibrationTelemetryPublisher(ARGS)
|
control_telemetry_publisher = ControlCalibrationTelemetryPublisher(ARGS)
|
||||||
@@ -2352,6 +2538,7 @@ def main():
|
|||||||
print(f" - /cmd_vel topic: {runtime.cmd_vel_topic}")
|
print(f" - /cmd_vel topic: {runtime.cmd_vel_topic}")
|
||||||
print(f" - external telemetry topic: {ARGS.external_telemetry_topic}")
|
print(f" - external telemetry topic: {ARGS.external_telemetry_topic}")
|
||||||
print(f" - chassis telemetry topic: {ARGS.chassis_telemetry_topic}")
|
print(f" - chassis telemetry topic: {ARGS.chassis_telemetry_topic}")
|
||||||
|
print(f" - vehicle TF: enabled={not ARGS.disable_vehicle_tf}, hz={ARGS.vehicle_tf_publish_hz}")
|
||||||
print(f" - control telemetry topic: {ARGS.control_telemetry_topic}")
|
print(f" - control telemetry topic: {ARGS.control_telemetry_topic}")
|
||||||
print(f" - sensor telemetry topic: {ARGS.sensor_telemetry_topic}")
|
print(f" - sensor telemetry topic: {ARGS.sensor_telemetry_topic}")
|
||||||
print(f" - scene manifest: {runtime.scene_manifest_path}")
|
print(f" - scene manifest: {runtime.scene_manifest_path}")
|
||||||
@@ -2399,6 +2586,7 @@ def main():
|
|||||||
|
|
||||||
world.step(render=render_frame)
|
world.step(render=render_frame)
|
||||||
telemetry_publisher.publish(agv)
|
telemetry_publisher.publish(agv)
|
||||||
|
vehicle_tf_publisher.publish(agv)
|
||||||
vehicle_imu_publisher.publish(agv)
|
vehicle_imu_publisher.publish(agv)
|
||||||
vehicle_laser_scan_publisher.publish(agv)
|
vehicle_laser_scan_publisher.publish(agv)
|
||||||
chassis_telemetry_publisher.publish(agv)
|
chassis_telemetry_publisher.publish(agv)
|
||||||
@@ -2406,6 +2594,7 @@ def main():
|
|||||||
sensor_telemetry_publisher.publish()
|
sensor_telemetry_publisher.publish()
|
||||||
finally:
|
finally:
|
||||||
telemetry_publisher.shutdown()
|
telemetry_publisher.shutdown()
|
||||||
|
vehicle_tf_publisher.shutdown()
|
||||||
vehicle_imu_publisher.shutdown()
|
vehicle_imu_publisher.shutdown()
|
||||||
vehicle_laser_scan_publisher.shutdown()
|
vehicle_laser_scan_publisher.shutdown()
|
||||||
chassis_telemetry_publisher.shutdown()
|
chassis_telemetry_publisher.shutdown()
|
||||||
|
|||||||
Reference in New Issue
Block a user