fix control stack startup and duplicate config handling
This commit is contained in:
@@ -6,6 +6,8 @@ from std_msgs.msg import String
|
|||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
|
||||||
class ControlManager(Node):
|
class ControlManager(Node):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -17,27 +19,60 @@ class ControlManager(Node):
|
|||||||
durability=DurabilityPolicy.TRANSIENT_LOCAL,
|
durability=DurabilityPolicy.TRANSIENT_LOCAL,
|
||||||
reliability=ReliabilityPolicy.RELIABLE
|
reliability=ReliabilityPolicy.RELIABLE
|
||||||
)
|
)
|
||||||
self.planning_process = None
|
self.control_process = None
|
||||||
|
self.control_config = None
|
||||||
|
|
||||||
self.create_subscription(String, '/vehicle_config', self.handle_config, qos_profile=qos)
|
self.create_subscription(String, '/vehicle_config', self.handle_config, qos_profile=qos)
|
||||||
|
|
||||||
def handle_config(self, msg:String):
|
def handle_config(self, msg:String):
|
||||||
self.get_logger().info(f"Control Received config: {msg.data}")
|
try:
|
||||||
config_data = json.loads(msg.data)
|
config_data = json.loads(msg.data)
|
||||||
|
except json.JSONDecodeError:
|
||||||
self.planning_process = subprocess.Popen(['ros2', 'launch',
|
self.get_logger().error('Invalid /vehicle_config payload.')
|
||||||
'control_pkg',
|
return
|
||||||
'nav2_control.launch.py',
|
|
||||||
f'sim_mode:={config_data.get("sim_mode")}',
|
config = {
|
||||||
f'robot:={config_data.get("robot")}',
|
'sim_mode': config_data.get('sim_mode'),
|
||||||
f'level:={config_data.get("level")}'])
|
'robot': config_data.get('robot'),
|
||||||
pass
|
'level': config_data.get('level'),
|
||||||
|
}
|
||||||
|
if self.control_process and self.control_process.poll() is None:
|
||||||
|
if config == self.control_config:
|
||||||
|
self.get_logger().info('Control stack is already running for this vehicle configuration.')
|
||||||
|
return
|
||||||
|
self.get_logger().warn('Vehicle configuration changed. Restarting the control stack.')
|
||||||
|
self.stop_control_process()
|
||||||
|
|
||||||
|
self.get_logger().info(f'Received vehicle config: {config}')
|
||||||
|
self.control_process = subprocess.Popen(
|
||||||
|
[
|
||||||
|
'ros2', 'launch', 'control_pkg', 'nav2_control.launch.py',
|
||||||
|
f'sim_mode:={config["sim_mode"]}',
|
||||||
|
f'robot:={config["robot"]}',
|
||||||
|
f'level:={config["level"]}',
|
||||||
|
],
|
||||||
|
start_new_session=True,
|
||||||
|
)
|
||||||
|
self.control_config = config
|
||||||
|
|
||||||
|
def stop_control_process(self):
|
||||||
|
if not self.control_process or self.control_process.poll() is not None:
|
||||||
|
self.control_process = None
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.killpg(os.getpgid(self.control_process.pid), signal.SIGTERM)
|
||||||
|
self.control_process.wait(timeout=5)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
os.killpg(os.getpgid(self.control_process.pid), signal.SIGKILL)
|
||||||
|
self.control_process.wait()
|
||||||
|
except ProcessLookupError:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
self.control_process = None
|
||||||
|
|
||||||
def destroy_node(self):
|
def destroy_node(self):
|
||||||
if self.planning_process:
|
self.stop_control_process()
|
||||||
self.planning_process.terminate()
|
|
||||||
self.planning_process.wait()
|
|
||||||
|
|
||||||
return super().destroy_node()
|
return super().destroy_node()
|
||||||
|
|
||||||
def main(args=None):
|
def main(args=None):
|
||||||
@@ -54,4 +89,3 @@ def main(args=None):
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
||||||
@@ -4,7 +4,7 @@ from ament_index_python.packages import get_package_share_directory
|
|||||||
from launch import LaunchDescription
|
from launch import LaunchDescription
|
||||||
from launch.actions import DeclareLaunchArgument
|
from launch.actions import DeclareLaunchArgument
|
||||||
from launch.actions import GroupAction
|
from launch.actions import GroupAction
|
||||||
from launch.actions import OpaqueFunction
|
from launch.actions import OpaqueFunction, TimerAction
|
||||||
from launch.substitutions import LaunchConfiguration, PathJoinSubstitution
|
from launch.substitutions import LaunchConfiguration, PathJoinSubstitution
|
||||||
from launch.substitutions import PythonExpression
|
from launch.substitutions import PythonExpression
|
||||||
from launch_ros.actions import Node
|
from launch_ros.actions import Node
|
||||||
@@ -80,15 +80,18 @@ def launch_setup(context, *args, **kwargs):
|
|||||||
parameters=[configured_params],
|
parameters=[configured_params],
|
||||||
arguments=['--ros-args', '--log-level', log_level],
|
arguments=['--ros-args', '--log-level', log_level],
|
||||||
),
|
),
|
||||||
Node(
|
TimerAction(
|
||||||
package='nav2_lifecycle_manager',
|
period=2.0,
|
||||||
executable='lifecycle_manager',
|
actions=[Node(
|
||||||
name='lifecycle_manager_control',
|
package='nav2_lifecycle_manager',
|
||||||
output='screen',
|
executable='lifecycle_manager',
|
||||||
arguments=['--ros-args', '--log-level', log_level],
|
name='lifecycle_manager_control',
|
||||||
parameters=[{'use_sim_time': sim_mode == 'true'},
|
output='screen',
|
||||||
{'autostart': True},
|
arguments=['--ros-args', '--log-level', log_level],
|
||||||
{'node_names': lifecycle_nodes}]
|
parameters=[{'use_sim_time': sim_mode == 'true'},
|
||||||
|
{'autostart': True},
|
||||||
|
{'node_names': lifecycle_nodes}]
|
||||||
|
)],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user