The MCP server is now a ROS node and the only way in or out, per the owner's decision that ROS never leaves the robot. It serves MCP on the USB address only with a bearer token, keeps the stable tool name look() (which waits for the next camera frame, so it is never stale, and passes the camera's JPEG through), and registers turn_head/head_position only when a head is fitted, which today it is not. While an authenticated client is connected it republishes the agent heartbeat on /hermes/state, so the face wakes on connect and sleeps about 40 s after the client leaves; look() also makes the face squint. The token is generated on the Pi into /etc/robot-mcp.env (root:hermes, 640) and never printed. Verified end to end from talos with a real MCP client over the USB link: tools listed, a 157 KB JPEG back in 0.08 s, a wrong token refused, the Wi-Fi path dropped by the firewall. Host config snippets now point at 10.55.0.2. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AFN8nRRCiDiMN7ChBpHJhs
47 lines
2.3 KiB
Python
47 lines
2.3 KiB
Python
"""Bring up the robot: camera, face, face detection, audio levels, and the MCP bridge.
|
|
|
|
ros2 launch hermes_bringup robot.launch.py [camera:=false] [face:=false] [vision:=false] [audio:=false] [mcp:=false]
|
|
[start_delay:=6.0]
|
|
|
|
Every node is its own process and is respawned if it exits, so one crash cannot take the others
|
|
down: the face keeps running when the camera does not. Parameters are in config/robot.yaml.
|
|
|
|
The face starts at once; the camera, vision, audio and MCP nodes wait start_delay seconds, so the four
|
|
Python processes do not fight over the CPU while the face imports and draws its first frame.
|
|
"""
|
|
import os
|
|
|
|
from ament_index_python.packages import get_package_share_directory
|
|
from launch import LaunchDescription
|
|
from launch.actions import DeclareLaunchArgument, TimerAction
|
|
from launch.conditions import IfCondition
|
|
from launch.substitutions import LaunchConfiguration
|
|
from launch_ros.actions import Node
|
|
|
|
# (launch argument, package, executable, node name in robot.yaml)
|
|
NODES = [
|
|
('camera', 'camera_ros', 'camera_node', 'camera'),
|
|
('face', 'hermes_face', 'face_node', 'hermes_face'),
|
|
('vision', 'hermes_vision', 'face_detector', 'hermes_face_detector'),
|
|
('audio', 'hermes_audio', 'audio_state', 'hermes_audio_state'),
|
|
('mcp', 'hermes_mcp', 'mcp_server', 'hermes_mcp'),
|
|
]
|
|
|
|
|
|
DELAYED = {'camera', 'vision', 'audio', 'mcp'}
|
|
|
|
|
|
def generate_launch_description():
|
|
params = os.path.join(get_package_share_directory('hermes_bringup'), 'config', 'robot.yaml')
|
|
actions = [DeclareLaunchArgument(arg, default_value='true', description=f'run the {name} node')
|
|
for arg, _, _, name in NODES]
|
|
actions.append(DeclareLaunchArgument('start_delay', default_value='6.0',
|
|
description='seconds the other nodes wait for the face'))
|
|
for arg, package, executable, name in NODES:
|
|
node = Node(package=package, executable=executable, name=name, output='screen',
|
|
parameters=[params], respawn=True, respawn_delay=2.0,
|
|
condition=IfCondition(LaunchConfiguration(arg)))
|
|
actions.append(TimerAction(period=LaunchConfiguration('start_delay'), actions=[node])
|
|
if arg in DELAYED else node)
|
|
return LaunchDescription(actions)
|