import os import pytest import yaml pytest.importorskip('launch_ros') HERE = os.path.dirname(__file__) ROOT = os.path.dirname(HERE) def test_launch_description_has_one_respawning_process_per_node(): import importlib.util spec = importlib.util.spec_from_file_location('robot_launch', os.path.join(ROOT, 'launch', 'robot.launch.py')) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) from launch.actions import TimerAction from launch_ros.actions import Node ld = mod.generate_launch_description() nodes = [e for e in ld.entities if isinstance(e, Node)] delayed = [n for t in ld.entities if isinstance(t, TimerAction) for n in t.actions if isinstance(n, Node)] assert len(nodes) + len(delayed) == len(mod.NODES) == 6 assert len(nodes) == 1 and 'face' in mod.NODES[1] and len(delayed) == 5 # the face starts at once # respawn lives on a private attribute that differs between ROS releases, so check the source src = open(os.path.join(ROOT, 'launch', 'robot.launch.py')).read() assert 'respawn=True' in src and 'respawn_delay' in src def test_every_node_in_the_launch_file_has_parameters_and_the_face_is_mounted_portrait(): cfg = yaml.safe_load(open(os.path.join(ROOT, 'config', 'robot.yaml'))) for name in ('camera', 'hermes_face', 'hermes_face_detector', 'hermes_audio_state', 'hermes_mcp', 'hermes_thermal'): assert 'ros__parameters' in cfg[name], name assert cfg['hermes_face']['ros__parameters']['rotation'] == 270 assert cfg['camera']['ros__parameters']['FrameDurationLimits'] == [66666, 66666] def test_the_mcp_bridge_binds_the_usb_link_and_has_no_head_yet(): cfg = yaml.safe_load(open(os.path.join(ROOT, 'config', 'robot.yaml')))['hermes_mcp']['ros__parameters'] assert cfg['bind'] == '10.55.0.2' and cfg['port'] == 8765 and cfg['head'] is False assert 'token' not in ' '.join(cfg) # the secret lives in an env file, not here def test_the_thermal_guards_normal_rates_equal_the_real_ones(): cfg = yaml.safe_load(open(os.path.join(ROOT, 'config', 'robot.yaml'))) t = cfg['hermes_thermal']['ros__parameters'] d = cfg['camera']['ros__parameters']['FrameDurationLimits'][0] assert abs(t['camera_fps'] - 1e6 / d) < 0.01 assert t['face_fps'] == cfg['hermes_face']['ros__parameters']['fps'] assert t['detector_hz'] == cfg['hermes_face_detector']['ros__parameters']['rate_hz'] assert t['warm_c'] < t['hot_c'] < t['critical_c'] < 85.0 # all below the Pi's hard limit