Skip to content

Commit 3aa49ff

Browse files
iserverobotics-bonaalexlin2
authored andcommitted
add lifecycle node conversion and foxglove utilities
- Convert all 7 SLAM nodes to lifecycle nodes (FASTLIO2 + arise_slam) - Add handle_once=True to prevent re-activation loops - Add foxglove relay scripts (twist, goal/autonomy) - Update Docker and launch files
1 parent e9893df commit 3aa49ff

Some content is hidden

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

45 files changed

+1524
-133
lines changed

docker/.env

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,18 @@ USE_RVIZ=false
5757
# Set to file prefix (no .pcd extension), e.g., /ros2_ws/maps/warehouse
5858
MAP_PATH=
5959

60+
# ============================================
61+
# ROS Bridge Server (TCP API for programmatic control)
62+
# ============================================
63+
# Bind address (0.0.0.0 = all interfaces)
64+
#ROS_BRIDGE_HOST=0.0.0.0
65+
66+
# TCP port for the bridge API
67+
#ROS_BRIDGE_PORT=9090
68+
69+
# Default autonomy speed (0.0 to 1.0)
70+
#ROS_BRIDGE_AUTONOMY_SPEED=1.0
71+
6072
# ============================================
6173
# Device Group IDs
6274
# ============================================

docker/Dockerfile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,11 @@ COPY system_bagfile.sh ${WORKSPACE}/
210210
COPY system_bagfile_with_route_planner.sh ${WORKSPACE}/
211211
COPY system_bagfile_with_exploration_planner.sh ${WORKSPACE}/
212212

213+
# Copy foxglove relay scripts
214+
COPY docker/foxglove_utility/twist_relay.py /usr/local/bin/twist_relay.py
215+
COPY docker/foxglove_utility/goal_autonomy_relay.py /usr/local/bin/goal_autonomy_relay.py
216+
RUN chmod +x /usr/local/bin/twist_relay.py /usr/local/bin/goal_autonomy_relay.py
217+
213218
# Create directories for maps and logs
214219
RUN mkdir -p ${WORKSPACE}/maps ${WORKSPACE}/logs
215220

@@ -270,6 +275,10 @@ if [ "${ENABLE_WIFI_BUFFER}" = "true" ]; then
270275
sysctl -w net.core.wmem_max=67108864 net.core.wmem_default=67108864 2>/dev/null || true
271276
fi
272277

278+
# Launch foxglove relay scripts in background
279+
python3 /usr/local/bin/twist_relay.py &
280+
python3 /usr/local/bin/goal_autonomy_relay.py &
281+
273282
exec "$@"
274283
ENTRYPOINT_EOF
275284
RUN chmod +x /ros_entrypoint.sh

docker/docker-compose.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ services:
4141
- ENABLE_WIFI_BUFFER=${ENABLE_WIFI_BUFFER:-false}
4242
- USE_RVIZ=${USE_RVIZ:-false}
4343
- MAP_PATH=${MAP_PATH:-}
44+
# ROS Bridge Server (TCP API for programmatic control, port 9090)
45+
- ROS_BRIDGE_HOST=${ROS_BRIDGE_HOST:-0.0.0.0}
46+
- ROS_BRIDGE_PORT=${ROS_BRIDGE_PORT:-9090}
47+
- ROS_BRIDGE_AUTONOMY_SPEED=${ROS_BRIDGE_AUTONOMY_SPEED:-1.0}
4448

4549
volumes:
4650
# X11 for GUI (RViz)
@@ -72,7 +76,14 @@ services:
7276
- -c
7377
- |
7478
echo "Starting real robot system with route planner..."
75-
ros2 launch vehicle_simulator system_real_robot_with_route_planner.launch.py use_fastlio2:=true &
79+
echo " ROS Bridge Server: port $${ROS_BRIDGE_PORT:-9090} (TCP API)"
80+
echo " Foxglove Bridge: port 8765 (WebSocket)"
81+
ros2 launch vehicle_simulator system_real_robot_with_route_planner.launch.py \
82+
use_fastlio2:=true \
83+
enable_bridge:=true \
84+
bridge_host:=$${ROS_BRIDGE_HOST:-0.0.0.0} \
85+
bridge_port:=$${ROS_BRIDGE_PORT:-9090} \
86+
bridge_autonomy_speed:=$${ROS_BRIDGE_AUTONOMY_SPEED:-1.0} &
7687
NAV_PID=$$!
7788
sleep 2
7889
echo "Starting Foxglove Bridge on port 8765..."

docker/fix.txt

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Fixes needed for nav_autonomy Docker image
2+
# Apply these changes to the Dockerfile, then rebuild and push
3+
4+
# ============================================================================
5+
# FIX 1: Add unitree_webrtc_connect to Dockerfile
6+
# ============================================================================
7+
# Add after line ~357 (after portaudio19-dev install):
8+
9+
# Install unitree_webrtc_connect for Unitree Go2 robot control
10+
# Clone, fix missing __init__.py files (upstream bug), then install
11+
RUN cd /tmp && \
12+
git clone https://github.com/VectorRobotics/unitree_webrtc_connect.git && \
13+
touch /tmp/unitree_webrtc_connect/go2_webrtc_driver/msgs/__init__.py && \
14+
touch /tmp/unitree_webrtc_connect/go2_webrtc_driver/lidar/__init__.py && \
15+
pip3 install --break-system-packages /tmp/unitree_webrtc_connect && \
16+
rm -rf /tmp/unitree_webrtc_connect
17+
18+
# ============================================================================
19+
# FIX 2: Add relay scripts to Dockerfile
20+
# ============================================================================
21+
# Add after the COPY commands for config files (around line ~387):
22+
23+
COPY docker/navigation/foxglove_utility/twist_relay.py /usr/local/bin/twist_relay.py
24+
COPY docker/navigation/foxglove_utility/goal_autonomy_relay.py /usr/local/bin/goal_autonomy_relay.py
25+
RUN chmod +x /usr/local/bin/twist_relay.py /usr/local/bin/goal_autonomy_relay.py
26+
27+
# ============================================================================
28+
# FIX 3: Ensure pip3 is available for installing Python packages
29+
# ============================================================================
30+
# Add to the apt-get install section (around line ~25):
31+
32+
python3-pip \
33+
34+
# ============================================================================
35+
# Summary of files to modify:
36+
# ============================================================================
37+
# 1. docker/navigation/Dockerfile - add the above changes
38+
# 2. Ensure docker/navigation/foxglove_utility/twist_relay.py exists
39+
# 3. Ensure docker/navigation/foxglove_utility/goal_autonomy_relay.py exists
40+
41+
# ============================================================================
42+
# Rebuild commands (run from dimos repo root):
43+
# ============================================================================
44+
# cd /path/to/dimos
45+
# docker compose -f docker/navigation/docker-compose.yml build
46+
# docker tag dimos_autonomy_stack:jazzy iserverobotics/nav_autonomy:latest
47+
# docker push iserverobotics/nav_autonomy:latest
48+
49+
# Or for multi-arch:
50+
# docker buildx build --platform linux/arm64 -t iserverobotics/nav_autonomy:latest --push -f docker/navigation/Dockerfile .
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Relay node that publishes Joy message to enable autonomy mode when goal_pose is received.
4+
Mimics the behavior of the goalpoint_rviz_plugin for Foxglove compatibility.
5+
"""
6+
7+
from geometry_msgs.msg import PointStamped, PoseStamped
8+
import rclpy
9+
from rclpy.node import Node
10+
from rclpy.qos import DurabilityPolicy, HistoryPolicy, QoSProfile, ReliabilityPolicy
11+
from sensor_msgs.msg import Joy
12+
13+
14+
class GoalAutonomyRelay(Node):
15+
def __init__(self):
16+
super().__init__("goal_autonomy_relay")
17+
18+
# QoS for goal topics (match foxglove_bridge)
19+
goal_qos = QoSProfile(
20+
reliability=ReliabilityPolicy.RELIABLE,
21+
history=HistoryPolicy.KEEP_LAST,
22+
durability=DurabilityPolicy.VOLATILE,
23+
depth=5,
24+
)
25+
26+
# Subscribe to goal_pose (PoseStamped from Foxglove)
27+
self.pose_sub = self.create_subscription(
28+
PoseStamped, "/goal_pose", self.goal_pose_callback, goal_qos
29+
)
30+
31+
# Subscribe to way_point (PointStamped from Foxglove)
32+
self.point_sub = self.create_subscription(
33+
PointStamped, "/way_point", self.way_point_callback, goal_qos
34+
)
35+
36+
# Publisher for Joy message to enable autonomy
37+
self.joy_pub = self.create_publisher(Joy, "/joy", 5)
38+
39+
self.get_logger().info(
40+
"Goal autonomy relay started - will publish Joy to enable autonomy when goals are received"
41+
)
42+
43+
def publish_autonomy_joy(self):
44+
"""Publish Joy message that enables autonomy mode (mimics goalpoint_rviz_plugin)"""
45+
joy = Joy()
46+
joy.header.stamp = self.get_clock().now().to_msg()
47+
joy.header.frame_id = "goal_autonomy_relay"
48+
49+
# axes[2] = -1.0 enables autonomy mode in pathFollower
50+
# axes[4] = 1.0 sets forward speed
51+
# axes[5] = 1.0 enables obstacle checking
52+
joy.axes = [0.0, 0.0, -1.0, 0.0, 1.0, 1.0, 0.0, 0.0]
53+
54+
# buttons[7] = 1 (same as RViz plugin)
55+
joy.buttons = [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]
56+
57+
self.joy_pub.publish(joy)
58+
self.get_logger().info("Published Joy message to enable autonomy mode")
59+
60+
def goal_pose_callback(self, msg: PoseStamped):
61+
self.get_logger().info(
62+
f"Received goal_pose at ({msg.pose.position.x:.2f}, {msg.pose.position.y:.2f})"
63+
)
64+
self.publish_autonomy_joy()
65+
66+
def way_point_callback(self, msg: PointStamped):
67+
self.get_logger().info(f"Received way_point at ({msg.point.x:.2f}, {msg.point.y:.2f})")
68+
self.publish_autonomy_joy()
69+
70+
71+
def main(args=None):
72+
rclpy.init(args=args)
73+
node = GoalAutonomyRelay()
74+
try:
75+
rclpy.spin(node)
76+
except KeyboardInterrupt:
77+
pass
78+
finally:
79+
node.destroy_node()
80+
rclpy.shutdown()
81+
82+
83+
if __name__ == "__main__":
84+
main()
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Simple relay node that converts geometry_msgs/Twist to geometry_msgs/TwistStamped.
4+
Used for Foxglove Teleop panel which only publishes Twist.
5+
"""
6+
7+
from geometry_msgs.msg import Twist, TwistStamped
8+
import rclpy
9+
from rclpy.node import Node
10+
from rclpy.qos import HistoryPolicy, QoSProfile, ReliabilityPolicy
11+
12+
13+
class TwistRelay(Node):
14+
def __init__(self):
15+
super().__init__("twist_relay")
16+
17+
# Declare parameters
18+
self.declare_parameter("input_topic", "/foxglove_teleop")
19+
self.declare_parameter("output_topic", "/cmd_vel")
20+
self.declare_parameter("frame_id", "vehicle")
21+
22+
input_topic = self.get_parameter("input_topic").value
23+
output_topic = self.get_parameter("output_topic").value
24+
self.frame_id = self.get_parameter("frame_id").value
25+
26+
# QoS for real-time control
27+
qos = QoSProfile(
28+
reliability=ReliabilityPolicy.BEST_EFFORT, history=HistoryPolicy.KEEP_LAST, depth=1
29+
)
30+
31+
# Subscribe to Twist (from Foxglove Teleop)
32+
self.subscription = self.create_subscription(Twist, input_topic, self.twist_callback, qos)
33+
34+
# Publish TwistStamped
35+
self.publisher = self.create_publisher(TwistStamped, output_topic, qos)
36+
37+
self.get_logger().info(
38+
f"Twist relay: {input_topic} (Twist) -> {output_topic} (TwistStamped)"
39+
)
40+
41+
def twist_callback(self, msg: Twist):
42+
stamped = TwistStamped()
43+
stamped.header.stamp = self.get_clock().now().to_msg()
44+
stamped.header.frame_id = self.frame_id
45+
stamped.twist = msg
46+
self.publisher.publish(stamped)
47+
48+
49+
def main(args=None):
50+
rclpy.init(args=args)
51+
node = TwistRelay()
52+
try:
53+
rclpy.spin(node)
54+
except KeyboardInterrupt:
55+
pass
56+
finally:
57+
node.destroy_node()
58+
rclpy.shutdown()
59+
60+
61+
if __name__ == "__main__":
62+
main()

src/base_autonomy/vehicle_simulator/launch/system_bagfile.launch.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ def generate_launch_description():
1818
vehicleX = LaunchConfiguration('vehicleX')
1919
vehicleY = LaunchConfiguration('vehicleY')
2020
checkTerrainConn = LaunchConfiguration('checkTerrainConn')
21+
enable_bridge = LaunchConfiguration('enable_bridge')
22+
bridge_host = LaunchConfiguration('bridge_host')
23+
bridge_port = LaunchConfiguration('bridge_port')
24+
bridge_autonomy_speed = LaunchConfiguration('bridge_autonomy_speed')
2125

2226
declare_use_sim_time = DeclareLaunchArgument(
2327
'use_sim_time',
@@ -36,6 +40,10 @@ def generate_launch_description():
3640
declare_vehicleX = DeclareLaunchArgument('vehicleX', default_value='0.0', description='')
3741
declare_vehicleY = DeclareLaunchArgument('vehicleY', default_value='0.0', description='')
3842
declare_checkTerrainConn = DeclareLaunchArgument('checkTerrainConn', default_value='true', description='')
43+
declare_enable_bridge = DeclareLaunchArgument('enable_bridge', default_value='true', description='Enable ROS bridge server')
44+
declare_bridge_host = DeclareLaunchArgument('bridge_host', default_value='0.0.0.0', description='ROS bridge bind address')
45+
declare_bridge_port = DeclareLaunchArgument('bridge_port', default_value='9090', description='ROS bridge port')
46+
declare_bridge_autonomy_speed = DeclareLaunchArgument('bridge_autonomy_speed', default_value='1.0', description='Default autonomy speed')
3947

4048
start_local_planner = IncludeLaunchDescription(
4149
PythonLaunchDescriptionSource(os.path.join(
@@ -107,6 +115,19 @@ def generate_launch_description():
107115
}]
108116
)
109117

118+
start_ros_bridge = Node(
119+
package='ros_bridge_server',
120+
executable='ros_bridge_server',
121+
name='ros_bridge_server',
122+
output='screen',
123+
arguments=[
124+
'--host', bridge_host,
125+
'--port', bridge_port,
126+
'--autonomy-speed', bridge_autonomy_speed,
127+
],
128+
condition=IfCondition(enable_bridge),
129+
)
130+
110131
ld = LaunchDescription()
111132

112133
# Add the actions
@@ -120,6 +141,10 @@ def generate_launch_description():
120141
ld.add_action(declare_vehicleX)
121142
ld.add_action(declare_vehicleY)
122143
ld.add_action(declare_checkTerrainConn)
144+
ld.add_action(declare_enable_bridge)
145+
ld.add_action(declare_bridge_host)
146+
ld.add_action(declare_bridge_port)
147+
ld.add_action(declare_bridge_autonomy_speed)
123148

124149
ld.add_action(start_local_planner)
125150
ld.add_action(start_terrain_analysis)
@@ -129,5 +154,6 @@ def generate_launch_description():
129154
ld.add_action(start_fastlio2)
130155
ld.add_action(start_visualization_tools)
131156
ld.add_action(start_joy)
157+
ld.add_action(start_ros_bridge)
132158

133159
return ld

src/base_autonomy/vehicle_simulator/launch/system_bagfile_with_exploration_planner.launch.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ def generate_launch_description():
1919
vehicleX = LaunchConfiguration('vehicleX')
2020
vehicleY = LaunchConfiguration('vehicleY')
2121
checkTerrainConn = LaunchConfiguration('checkTerrainConn')
22+
enable_bridge = LaunchConfiguration('enable_bridge')
23+
bridge_host = LaunchConfiguration('bridge_host')
24+
bridge_port = LaunchConfiguration('bridge_port')
25+
bridge_autonomy_speed = LaunchConfiguration('bridge_autonomy_speed')
2226

2327
declare_use_sim_time = DeclareLaunchArgument(
2428
'use_sim_time',
@@ -38,6 +42,10 @@ def generate_launch_description():
3842
declare_vehicleX = DeclareLaunchArgument('vehicleX', default_value='0.0', description='')
3943
declare_vehicleY = DeclareLaunchArgument('vehicleY', default_value='0.0', description='')
4044
declare_checkTerrainConn = DeclareLaunchArgument('checkTerrainConn', default_value='true', description='')
45+
declare_enable_bridge = DeclareLaunchArgument('enable_bridge', default_value='true', description='Enable ROS bridge server')
46+
declare_bridge_host = DeclareLaunchArgument('bridge_host', default_value='0.0.0.0', description='ROS bridge bind address')
47+
declare_bridge_port = DeclareLaunchArgument('bridge_port', default_value='9090', description='ROS bridge port')
48+
declare_bridge_autonomy_speed = DeclareLaunchArgument('bridge_autonomy_speed', default_value='1.0', description='Default autonomy speed')
4149

4250
start_local_planner = IncludeLaunchDescription(
4351
PythonLaunchDescriptionSource(os.path.join(
@@ -117,6 +125,19 @@ def generate_launch_description():
117125
}.items()
118126
)
119127

128+
start_ros_bridge = Node(
129+
package='ros_bridge_server',
130+
executable='ros_bridge_server',
131+
name='ros_bridge_server',
132+
output='screen',
133+
arguments=[
134+
'--host', bridge_host,
135+
'--port', bridge_port,
136+
'--autonomy-speed', bridge_autonomy_speed,
137+
],
138+
condition=IfCondition(enable_bridge),
139+
)
140+
120141
ld = LaunchDescription()
121142

122143
# Add the actions
@@ -131,6 +152,10 @@ def generate_launch_description():
131152
ld.add_action(declare_vehicleX)
132153
ld.add_action(declare_vehicleY)
133154
ld.add_action(declare_checkTerrainConn)
155+
ld.add_action(declare_enable_bridge)
156+
ld.add_action(declare_bridge_host)
157+
ld.add_action(declare_bridge_port)
158+
ld.add_action(declare_bridge_autonomy_speed)
134159

135160
ld.add_action(start_local_planner)
136161
ld.add_action(start_terrain_analysis)
@@ -141,5 +166,6 @@ def generate_launch_description():
141166
ld.add_action(start_visualization_tools)
142167
ld.add_action(start_joy)
143168
ld.add_action(start_tare_planner)
169+
ld.add_action(start_ros_bridge)
144170

145171
return ld

0 commit comments

Comments
 (0)