Skip to content

ROS 2 Lifecycle Nodes (packages/)

Part of the OpenRAL public-symbol inventory. Hand-curated; (LNN) markers are refreshed by tools/refresh_methods_linenos.py.

Thin wrappers around the Python-layer adapters; each exposes a single main() entry point for ros2 run.

packages/openral_hal_node/openral_hal_node/lifecycle_node.py

  • main() -> None (L28) — The one manifest-driven HAL lifecycle node every robot runs: openral_hal.lifecycle.make_lifecycle_main_from_manifest(node_name="openral_hal_node"), i.e. a ManifestHALLifecycleNode that builds its HAL from the robot_yaml + hal_mode parameters via openral_hal.build_hal. Heartbeat, /openral/safe_action consumer and /openral/estop latch come from HALLifecycleNodeBase. openral deploy sim|run launches it under the ROS node NAME openral_hal_<robot_id> (a __node:= remap — not a package name). Covered by packages/openral_hal_node/test/ (parametrised by robot manifest) and tests/integration/test_openarm_hal_lifecycle.py.

packages/world_state/openral_world_state_ros/lifecycle_node.py

  • main() -> None (L73) — WorldStateAggregator lifecycle node. Subscribes joint/policy/camera (camera_topic(name) per camera_names entry; the camera_topic_prefix parameter was removed by ADR-0108; object_depth_points_topic defaults to "" = no depth fallback)/attachment streams and publishes typed fast/slow world-state summaries; attachment updates atomically replace the prior set so multiple changes never expose a partial model; the envelope's place_declaration and grasp_declaration are relayed AttachmentState -> WorldStateStamped (_place_declaration_from_idl / _grasp_declaration_from_idl, _fill_place_declaration / _fill_grasp_declaration). Shared with rskill_runner_node.
  • _direct_image_frame_sensors() -> set[str] — The direct_image_frame_sensors parameter as a set; _on_image skips these cameras entirely, since the co-located sensor leg's pump already writes them to the aggregator at full cadence.
  • _IDL_DIAG_TO_STR: dict[int, str] (L949) — DiagnosticState.diag_status uint8 → the lowercase label ("ok"/"warn"/"stale"/"error") world_state_from_idl writes onto openral_core.WorldState.
  • world_state_from_idl(msg) -> WorldState (L957) — Symmetric inverse of build_world_state_stamped_msg: reconstructs an openral_core.WorldState from a WorldStateStamped msg. Used by the reasoner node to feed real state into its ContextRenderer; image_frames is always None (the IDL carries image topic refs, not inline pixels). Each detected object's bbox_3d comes from the detected_object_bbox_valid / _bbox_min / _bbox_max arrays (ignored unless index-aligned with the labels, so an older publisher decodes as no boxes); the reasoner seeds named grasp targets from it.
  • build_world_state_stamped_msg(node, world_state) -> WorldStateStamped (L1140) — Inverse translation: a Pydantic WorldState → WorldStateStamped IDL msg. Parallel arrays are deterministically ordered (sorted by key) so two consumers comparing snapshots at the same timestamp see the same byte layout.

packages/openral_nav2_bringup/openral_nav2_bringup/

  • _footprint_geometry.py — Footprint geometry shared by this package's nodes: convex_hull_2d (monotone-chain convex hull) and base_footprint_polygon (manifest polygon, else an N-gon around the radius). The costmaps' footprint is now static, substituted at launch — no node publishes it.
  • prop SHAPE_SPHERE, SHAPE_CAPSULE, SHAPE_BOX (_footprint_geometry.py L23–25) — Mirror openral_msgs/AttachedCollisionPrimitive's shape enum as plain ints, so this pure module imports without the colcon message overlay.
  • _MIN_POLYGON_VERTICES (_footprint_geometry.py L27) — 3; minimum distinct-vertex count convex_hull_2d accepts.
  • convex_hull_2d(points) -> list[tuple[float, float]] (_footprint_geometry.py L30) — Counter-clockwise convex hull via Andrew's monotone chain; drops collinear points, raises on fewer than _MIN_POLYGON_VERTICES distinct points.
  • base_footprint_polygon(description, *, circle_samples=12) -> list[tuple[float, float]] (_footprint_geometry.py L94) — The manifest's footprint_polygon if declared, else a circumscribed N-gon around footprint_radius.
  • payload_scan_filter_node.py — Removes a carried payload's and the robot's own silhouette from the /scan Nav2's costmaps read, republishing on /openral/nav2/scan. Every failure mode leaves more obstacles in the scan, never fewer, except a startup gate that withholds output until the self-TF first resolves.
  • DEFAULT_OUTPUT_TOPIC (payload_scan_filter_node.py L66) — "/openral/nav2/scan"; where the filtered scan is published — every costmap observation source and config/nav2_panda_mobile.yaml's collision monitor point here.
  • points_in_primitive(points_xyz, shape_type, shape_dimensions, transform, *, margin_m=0.0) (payload_scan_filter_node.py L69) — The kernel's surface_distance containment at zero slack, vectorised over points.
  • _MIN_POLYGON_VERTICES (payload_scan_filter_node.py L45) — Now imported from _footprint_geometry (formerly a local copy of the constant).
  • points_in_convex_polygon(points_xy, polygon, *, margin_m=0.0) (payload_scan_filter_node.py L140) — Half-plane containment test that verifies CCW convexity rather than assuming it (a concave or CW outline raises, so the node then removes nothing).
  • filter_scan_ranges(ranges, *, angle_min, angle_increment, range_min, range_max, placements, margin_m=0.0, self_polygon=None, base_from_scan=None, self_margin_m=0.0) (payload_scan_filter_node.py L208) — Sets removed beams to inf (Nav2 discards those: inf_is_valid defaults to False) and leaves readings already outside [range_min, range_max] alone.
  • main(args=None) -> None (payload_scan_filter_node.py L309) — ROS 2 entry point; defines PayloadScanFilterNode lazily inside so the module stays importable without the colcon overlay.
  • twist_to_action_relay_node.py — Opt-in (nav2.launch.py cmd_vel_relay:=true) relay of Nav2's /cmd_vel onto /openral/candidate_action as horizon-1 BODY_TWIST chunks via ROSPublishingHAL, so the safety kernel bounds base speed. Refuses (ROSConfigError) a manifest declaring base_joints (MobileBaseBridge owns that /cmd_vel, ADR-0040/0099) or lacking body_twist or either max_base_* speed bound; after cmd_vel_timeout_s (default 0.5 s, wall clock) of silence following a non-zero command it publishes one zero twist.
  • twist_to_body_twist_row(msg) -> tuple[float, ...] (twist_to_action_relay_node.py L45) — Twist → [linear.x, linear.y, 0, 0, 0, angular.z], the same mapping as MobileBaseBridge._on_cmd_vel.
  • main(args=None) -> None (twist_to_action_relay_node.py L57) — ROS 2 entry point; defines TwistToActionRelayNode lazily inside.

packages/openral_nav2_bringup/launch/nav2.launch.py

  • generate_launch_description() -> LaunchDescription (L84) — Stand-alone bring-up of the upstream Nav2 navigation_launch.py include; robot_yaml rewrites robot_radius/inflation_radius/motion_model over the shared base params file (lidar or, for slam_backend=visual, the visual variant) and clamps its velocity limits to the manifest's max_base_* bounds.
  • _cap_speeds(params, max_linear_m_s, max_angular_rad_s) -> None (L245) — Lowers velocity_smoother.max_velocity/min_velocity (the (vx, vy) corner scaled onto the ‖v‖ disk) and MPPI vx_max/wz_max in place to the kernel BODY_TWIST bounds; never raises a limit.
  • _speed_capped_params_file(params_file, max_linear_m_s, max_angular_rad_s) -> str (L292) — params_file unchanged when neither bound is set, else a temp copy with _cap_speeds applied (Jazzy RewrittenYaml cannot set a list element).
  • BOND_TIMEOUT_S (L60) — 30.0; how long a Nav2 lifecycle manager waits on a bond heartbeat before treating a descheduled server as dead. Not disabled (0.0), so a server that really dies is still caught.
  • DEFAULT_PARAMS_PATH (L402) — Default config/nav2_panda_mobile.yaml path; used by test/test_nav2_launch.py for hermetic argument validation without spawning a real ROS 2 graph.

packages/openral_perception_ros/openral_perception_ros/

  • main(args=None) -> None (ros_image_detector_node.py L222) — Standalone ROS-image object detector: subscribes a camera topic, runs an openral_runner backend (RT-DETR ONNX or an open-vocab VLM), and publishes detections as a PromptStamped. continuous mode publishes every frame; on_demand instead serves a one-shot locate_in_view query.
  • DETECTOR_LOG_LEVEL_ENV (ros_image_detector_node.py L79) — "OPENRAL_DETECTOR_LOG_LEVEL"; operators set this to debug to surface the continuous leg's per-publish DEBUG line, which the default INFO console hides.
  • _LOG_LEVEL_ALIASES (ros_image_detector_node.py L83) — Accepted spellings → the rclpy LoggingSeverity member name ("warning" → "WARN").
  • normalize_log_level(value) -> str | None (ros_image_detector_node.py L93) — Normalises an operator-supplied log level to an rclpy severity name; case-insensitive, whitespace-trimmed. None for an empty/unrecognised value so a typo never silences the node.
  • classify_continuous_tick(*, error, detection_count) -> tuple[str, str] (ros_image_detector_node.py L119) — Maps one continuous-leg detect tick to (log_level, message): a detect() exception → warning, an empty result → info liveness heartbeat, a non-empty result → debug.
  • camera_geometry_from_info(msg) -> tuple[str, IntrinsicsPinhole] (ros_image_detector_node.py L174) — A driver sensor_msgs/CameraInfo's (header.frame_id, K) via openral_hal.depth_cloud.intrinsics_from_camera_info; ROSConfigError when uncalibrated or frameless.
  • stamp_camera_geometry(md, geometry) -> ObjectsMetadata (ros_image_detector_node.py L197) — md re-validated with camera_frame_id/camera_intrinsics from geometry, or unchanged for None (no CameraInfo yet → the lift's logged manifest fallback). The node stamps every continuous batch and locate_in_view reply for cameras its camera_infos parameter names.
  • main(args=None) -> None (scene_vlm_node.py L46) — Scene-VLM query service node: caches each camera's latest frame and serves /openral/perception/query_scene, a free-text answer to a question about the current frame, from a QwenSceneVlm backend. The reasoning counterpart of locate_in_view.
  • main(args=None) -> None (reward_monitor_node.py L79) — Reward-monitor query service node: buffers the active VLA's camera frames and serves /openral/perception/query_task_progress from a reward backend (Robometer or TOPReward). A scoring heartbeat also drives the dashboard's progress bar and an optional Tier-C CriticScore publish. Advisory-only.
  • main(args=None) -> None (segmenter_node.py L749) — Entry point: init ROS, spin the segmenter node, shut down cleanly.
  • make_segmenter_node(node_name="openral_segmenter") -> Any (segmenter_node.py L734) — Constructs the real segmenter lifecycle node behind lazy ROS imports; the seam test_segment_in_view_service.py uses to stand up the node in-process. Node behaviour: Promptable-segmenter service node (kind: segmenter rSkill): serves /openral/perception/segment_in_view, turning a 3-D point into the pixel mask of the object there via an in-process SAM 2.1 model. Every failure path returns an empty failure_reason rather than raising. on_activate builds and warms the model and burns the handler's lazy openral_hal.depth_cloud / openral_core.geometry imports (the openral_hal import alone was 839 ms of a 1019 ms first call on Thor), logging the warm-up time.
  • DEFAULT_SEGMENT_SERVICE (segmenter_node.py L114) — "/openral/perception/segment_in_view"; the service name the HAL's vision attachment bridge calls by default.
  • DEFAULT_DEBUG_MASKS_TOPIC (segmenter_node.py L120) — "/openral/perception/masks"; diagnostic topic the latest mask set is re-published on when publish_debug_masks is enabled.
  • sensor_spec_by_name(description, name) -> SensorSpec | None (segmenter_node.py L123) — Finds a SensorSpec by name across sensors + sensor_bundles.
  • mono8_bytes_from_mask(mask) -> bytes (segmenter_node.py L213) — 255 set / 0 clear; raw mono8, deliberately not RLE (a one-shot event call does not justify a bespoke codec).
  • projection_geometry(spec, *, width, height, header_frame_id, camera_info=None) -> tuple[IntrinsicsPinhole, str, tuple[str, ...]] (segmenter_node.py L143) — Picks the K and optical frame a 3-D prompt is projected in: the cached image's header.frame_id over spec.frame_id (which may be a body frame — OpenArm head_zed), and the driver's live CameraInfo K (openral_hal.depth_cloud.intrinsics_from_camera_info) over the manifest's nominal intrinsics, rescaled to the frame via scale_intrinsics_to. Returns one note per fallback taken; the node logs each once at warning. Raises ROSConfigError on an uncalibrated CameraInfo or no K source at all.
  • parse_camera_entries(entries) -> dict[str, str] (camera_topics.py L23) — The bare "id=topic" parse (blank / half entries skipped, declaration order kept, possibly empty) behind resolve_camera_topics; used as-is for optional per-camera side streams such as segmenter_node's camera_infos. Pure and ROS-free.
  • resolve_camera_topics(entries, *, primary_camera, image_topic) -> dict[str, str] (camera_topics.py L49) — The one implementation of the camera-agnostic id=topic → ordered map every node in this package resolves its cameras with. Insertion order matters: the first key is the primary camera. Raises ROSConfigError when neither entries nor image_topic names a camera — every node's image_topic default is "" (ADR-0108), so a node the launch did not wire refuses to configure instead of subscribing to a guessed name. Pure and ROS-free.
  • image_to_bgr_bytes(msg) -> tuple[bytes, int, int] (image_convert.py L31) — The one sensor_msgs/Image → contiguous H·W·3 BGR uint8 converter for every perception node (segmenter, detector, scene VLM, reward monitor, depth provider). Accepts bgr8/8UC3, rgb8, bgra8/rgba8 (alpha dropped — the ZED's default layout), mono8/8UC1 (replicated); honours step, so pitch-padded rows are sliced off. No cv_bridge dependency. Callers log a refusal at WARNING (throttled 5 s) naming camera and encoding.
  • ImageConvertError (image_convert.py L27) — Raised by image_to_bgr_bytes on an unsupported encoding, a step shorter than a packed row, or a payload shorter than height*step.
  • SUPPORTED_ENCODINGS: frozenset[str] (image_convert.py L24) — the encodings image_to_bgr_bytes accepts.
  • depth_array_to_image_msg(depth_m, *, frame_id, stamp=None) -> Image (depth_convert.py L30) — Metric-depth message boundary (no torch). Builds a 32FC1 sensor_msgs/Image in metres (NaN = no return) with tightly-packed rows.
  • image_msg_to_depth_array(msg) -> ndarray (depth_convert.py L67) — Inverse of depth_array_to_image_msg.
  • camera_info_from_intrinsics(*, fx, fy, cx, cy, width, height, frame_id, stamp=None) -> CameraInfo (depth_convert.py L89) — Builds a plain pinhole CameraInfo (plumb_bob, zero distortion, K/P matrices). Used by depth_provider_node to feed nvblox.
  • camera_info_problem(k, info_wh, image_wh) -> str | None (metric_depth.py L57) — Why a CameraInfo cannot scale an image's DA3 depth: a non-positive / non-finite focal (an uncalibrated driver's all-zero K), an empty size, or another aspect ratio (a crop, another view). A uniform resize (a driver's full-resolution CameraInfo beside a downscaled republish) is usable. The metric provider drops such frames.
  • is_metric_model(model_id) -> bool (metric_depth.py L39) — Whether a DA3 checkpoint predicts canonical metric depth (DA3METRIC-*); the main series is not documented as metric.
  • scale_intrinsics(k, src_wh, dst_wh) -> tuple[float, float, float, float] (metric_depth.py L92) — (fx, fy, cx, cy) of an image resized (not cropped), pixel centres to pixel centres.
  • da3_metric_depth(canonical, *, fx, fy) -> ndarray (metric_depth.py L108) — DA3METRIC canonical output → metres (mean(fx, fy) * out / DA3_CANONICAL_FOCAL_PX, focal at the output raster); non-finite / non-positive → 0.0.
  • points_from_depth(depth, *, fx, fy, cx, cy, max_range_m) -> ndarray (metric_depth.py L123) — Metric raster → (N, 3) REP-103 optical cloud, dropping no-returns and z beyond max_range_m.
  • build_rectifier(k, size, model, coeffs) -> PinholeRectifier (lens_rectify.py L123) — The raw → exact-pinhole remap for one lens at one size, from a ROS CameraInfo lens model: plumb_bob / rational_polynomial (cv2.initUndistortRectifyMap) or equidistant fisheye (cv2.fisheye.initUndistortRectifyMap); no distortion → no remap. Valid pixels only (alpha/balance 0) and the camera's pixel aspect kept (one zoom); R (stereo) not applied, so the image stays in the camera's optical frame. Raises ValueError for what distortion_problem refuses.
  • PinholeRectifier (lens_rectify.py L98) — Frozen: k (rectified pinhole (fx, fy, cx, cy)), size, maps (None = already pinhole). The metric provider rectifies every frame before DA3 sees it, rebuilding only when the calibration or size changes.
  • PinholeRectifier.apply(image) -> ndarray (lens_rectify.py L111) — The rectified H x W x C image (cv2.remap, bilinear); the input itself when already pinhole.
  • distortion_problem(model, coeffs) -> str | None (lens_rectify.py L58) — Why a CameraInfo lens cannot be rectified: an unsupported model, a wrong coefficient count, a non-finite value; None when usable (incl. no distortion).
  • rectified_stream_k(p) -> tuple[float, float, float, float] (lens_rectify.py L84) — (fx, fy, cx, cy) of an already-rectified stream from CameraInfo.p (an image_rect topic shares the raw stream's CameraInfo; the deploy declares it with backend_params.rectified: true).
  • DISTORTION_COEFF_COUNTS: dict[str, int] (lens_rectify.py L47) — coefficients per supported model (plumb_bob 5, rational_polynomial 8, equidistant 4).
  • DA3_CANONICAL_FOCAL_PX = 300.0 (metric_depth.py L36) — the focal DA3METRIC's canonical depth is expressed at.
  • DepthConvertError (depth_convert.py L26) — Raised on non-2D depth, a non-32FC1 encoding, or a padded stride (step != width*4).
  • main(args=None) -> None (depth_provider_node.py L39) — Monocular metric-depth provider: forwards each RGB frame to the DA3 depth sidecar over ZMQ and republishes the metric depth as a 32FC1 image + CameraInfo for nvblox.launch.py. image_topic / depth_topic / camera_info_topic have no default (ADR-0108); an empty one raises ROSConfigError at startup. Best-effort; rebuilds its socket on a timed-out reply so a slow sidecar can't wedge depth forever. Metric mode (rgb_camera_info_topic set; DeployRuntime.estimated_depth_camera): refuses a reply whose model is not DA3METRIC-*, converts the canonical output to metres through the RGB CameraInfo focal scaled to DA3's raster (metric_depth), publishes depth + that scaled CameraInfo in depth_frame_id (the launch's resolved optical frame; empty = the CameraInfo's frame) and, with points_topic, the back-projected cloud (clipped at max_range_m, default 3.0); drops frames until a CameraInfo arrives.

packages/openral_slam_bringup/openral_slam_bringup/

  • main(args=None) -> None (depth_height_filter_node.py L434) — Nvblox floor-exclusion prefilter: derives a robot-relative navigation-height band from the manifest's collision geometry and zeroes depth pixels outside it before nvblox integrates them. Needed because nvblox's occupancy integrator still projects raw floor returns into /map. input_depth_topic / input_camera_info_topic have no default (ADR-0108); an empty one raises ROSConfigError.
  • RobotRelativeHeightBand (depth_height_filter_node.py L35) — Frozen dataclass: min_z_m/max_z_m navigation-height band plus source ("collision_geometry" or the min_body_height_m floor default).
  • quaternion_to_matrix_z_row(x, y, z, w) -> tuple[float, float, float] (depth_height_filter_node.py L43) — The rotation matrix's third row only — enough to project a shape's local z-extent onto the world z axis.
  • derive_robot_relative_height_band(description, *, floor_clearance_m=0.10, min_body_height_m=0.30) -> RobotRelativeHeightBand (depth_height_filter_node.py L294) — Measures the manifest's collision geometry (or falls back to min_body_height_m) to produce the navigation-height band.
  • filter_depth_by_global_height(depth_m, *, fx, fy, cx, cy, rotation_z_row, translation_z_m, min_height_m, max_height_m) (depth_height_filter_node.py L344) — Zeroes out-of-band/invalid pixels; raises ValueError on invalid intrinsics, shape, or band.
  • main(args=None) -> None (pycuvslam_node.py L221) — In-process cuVSLAM visual SLAM (PyCuVSLAM wheel): tracks a rectified stereo pair, or one RGB camera plus a metric-depth stream, publishes nav_msgs/Odometry, and broadcasts the map → odom TF edge. The pip-wheel alternative to the composable isaac_ros_visual_slam path. The four camera-topic params have no default (ADR-0108); _setup_inputs raises ROSConfigError naming any required one left empty (left always, right in stereo mode).
  • stereo_baseline_m(right_p) -> float (pycuvslam_node.py L47) — Raises ValueError unless the rectified right P encodes a positive baseline.
  • transform_to_pose(transform) (pycuvslam_node.py L134) — A geometry_msgs/Transform → rig_from_camera pose.
  • compose_pose(a, b) (pycuvslam_node.py L106) — Compose two ((qx,qy,qz,qw),(tx,ty,tz)) poses.
  • invert_pose(p) (pycuvslam_node.py L114) — Inverse of a ((qx,qy,qz,qw),(tx,ty,tz)) pose.
  • map_from_odom(map_from_rig, odom_from_rig) (pycuvslam_node.py L122) — Composes the tracked pose with the live odom ← rig TF to yield the map → odom edge.
  • depth_to_uint16_mm(msg, width, height, scale) (pycuvslam_node.py L160) — A 32FC1 metres Image → the uint16 grid cuVSLAM RGBD eats (PIL float-bilinear resize onto the RGB resolution, metres × scale clipped to uint16); raises ValueError on a non-32FC1 encoding.

packages/openral_octomap_bridge/launch/octomap_voxel_bridge.launch.py

  • generate_launch_description() -> LaunchDescription (L21) — Declares params (base_frame, octomap_topic, output_topic, resolution, coverage_radius_m, coverage_center_z, publish_rate_hz, max_octree_age_s) and spawns the octomap_voxel_bridge node that turns an OctoMap into the openral_msgs/OccupancyVoxels grid the safety kernel consumes.

packages/openral_hal_openarm/launch/real_bringup.launch.py

Bringup-only package: no HAL node of its own (OpenArm runs openral_hal_node); robots/openarm/robot.yaml reaches this file through hal.real_bringup.

  • generate_launch_description() -> LaunchDescription (L54) — Real-hardware ros2_control bringup for the OpenArm v2: includes upstream openarm_bringup's bimanual launch with this HAL's own CAN interface names. Never run alongside a deploy — two copies would double-publish /joint_states; activation energises the motors.
  • prop _LEFT_CAN_INTERFACE, _RIGHT_CAN_INTERFACE (L45–46) — "openarm_left" / "openarm_right"; must match the udev names openral_hal.openarm_real._LEFT_CAN_INTERFACE / _RIGHT_CAN_INTERFACE assign to the USB CAN-FD adapter's two channels.
  • _ROBOT_CONTROLLER (L51) — "joint_trajectory_controller"; must match the controller type OpenArmRealHAL publishes JointTrajectory messages to, not forward_position_controller (which takes Float64MultiArray).

packages/openral_slam_bringup/launch/

  • generate_launch_description() -> LaunchDescription (slam_toolbox.launch.py L40) — Stand-alone bring-up of upstream slam_toolbox/async_slam_toolbox_node as a LifecycleNode (auto-transitions only to INACTIVE; the Reasoner promotes to ACTIVE via LifecycleTransitionTool). Composed into deploy_e2e.launch.py when enable_slam:=true.
  • _SLAM_NODE_NAME (slam_toolbox.launch.py L26) — The async_slam_toolbox_node node name.
  • prop DEFAULT_PARAMS_PATH, NODE_NAME (slam_toolbox.launch.py L93–94) — Test-hermeticity re-exports used by packages/openral_slam_bringup/test/test_slam_toolbox_launch.py without spawning a real ROS 2 graph.
  • generate_launch_description() -> LaunchDescription (pycuvslam.launch.py L40) — Stand-alone bring-up of the in-process PyCuVSLAM stereo/mono-RGBD node (a plain Node, not a ComposableNodeContainer); the pip-wheel alternative to cuvslam.launch.py's NITROS path.
  • prop _NODE_NAME, _PACKAGE, _EXECUTABLE (pycuvslam.launch.py L26–28) — The pycuvslam_node node/package/executable names.
  • prop DEFAULT_PARAMS_PATH, NODE_NAME, PACKAGE, EXECUTABLE (pycuvslam.launch.py L140–143) — Test-hermeticity re-exports used by test/test_pycuvslam_launch.py without spawning a real ROS 2 graph or the cuVSLAM wheel.
  • generate_launch_description() -> LaunchDescription (nvblox.launch.py L55) — Stand-alone bring-up of the upstream nvblox::NvbloxNode composable node inside a ComposableNodeContainer, remapping its ~/static_occupancy_grid onto the backend-agnostic /map for lidar-less Nav2.
  • prop _NVBLOX_NODE_NAME, _NVBLOX_CONTAINER_NAME, _NVBLOX_PACKAGE, _NVBLOX_PLUGIN (nvblox.launch.py L38–43) — Node/container name and the Isaac ROS composable-node package+plugin id for NvbloxNode.
  • prop DEFAULT_PARAMS_PATH, NODE_NAME, PACKAGE, PLUGIN (nvblox.launch.py L193–196) — Test-hermeticity re-exports used by test/test_nvblox_launch.py without spawning a real ROS 2 graph or the nvblox engine.
  • generate_launch_description() -> LaunchDescription (cuvslam.launch.py L53) — Stand-alone bring-up of the upstream Isaac ROS VisualSlamNode (cuVSLAM) composable node inside a ComposableNodeContainer for NITROS-zero-copy image topics.
  • prop _VSLAM_NODE_NAME, _VSLAM_CONTAINER_NAME, _VSLAM_PACKAGE, _VSLAM_PLUGIN (cuvslam.launch.py L36–41) — Node/container name and the Isaac ROS composable-node package+plugin id for VisualSlamNode.
  • prop DEFAULT_PARAMS_PATH, NODE_NAME, PACKAGE, PLUGIN (cuvslam.launch.py L145–148) — Test-hermeticity re-exports used by test/test_cuvslam_launch.py without spawning a real ROS 2 graph or the cuVSLAM engine.

packages/openral_human_estop/openral_human_estop/forwarder_node.py

  • class HumanEstopForwarderNode(LifecycleNode) (L24) — Subscribes /openral/human_estop and republishes onto /openral/estop plus a FailureTrigger on /openral/failure/safety. No in-repo node currently publishes /openral/human_estop — the dashboard stop button publishes /openral/estop directly.
  • on_configure(state) -> TransitionCallbackReturn (L42) — Opens the estop publisher, the FailureTrigger publisher, and the /openral/human_estop subscription.
  • on_activate(state) -> TransitionCallbackReturn (L68) — No additional resources to start.
  • on_deactivate(state) -> TransitionCallbackReturn (L73) — No additional resources to stop.
  • on_cleanup(state) -> TransitionCallbackReturn (L78) — Releases the subscription + publishers.
  • on_shutdown(state) -> TransitionCallbackReturn (L92) — Forces cleanup (delegates to on_cleanup).
  • main(args=None) -> int (L122) — Entry point for ros2 run openral_human_estop forwarder_node.

packages/openral_safety_watchdog/openral_safety_watchdog/

  • estop_qos() -> QoSProfile (_qos.py L19) — QoS for /openral/estop: RELIABLE / VOLATILE / KEEP_LAST=10.
  • failure_qos() -> QoSProfile (_qos.py L30) — QoS for /openral/failure/safety: RELIABLE / VOLATILE / KEEP_LAST=50. Both factories are the single shared definition both nodes' on_configure calls, keeping their QoS identical by construction.
  • class DeadmanWatchdogNode(LifecycleNode) (deadman_watchdog_node.py L96) — Fires /openral/estop plus a timeout FailureTrigger when /openral/safe_action dies, in its own process so a kernel crash still brakes. arm_status_topic gates the deadline to an active goal; the post-estop latch releases only on a genuine SafetyStatus transition to clear.
  • prop DEFAULT_SAFE_ACTION_DEADLINE_S, DEFAULT_CHECK_PERIOD_S, DEFAULT_FIRST_CHUNK_DEADLINE_S, DEFAULT_SAFETY_STATUS_TOPIC, SAFETY_STATUS_LIVENESS_S (deadman_watchdog_node.py L49–64) — Defaults for the safe-action deadline (0.2 s), the deadline-check timer period (50 ms), the goal-accepted→first-chunk grace (60 s), the latched SafetyStatus topic, and the HZ-0096-1 liveness window (3 s) past which a SafetyStatus sample is treated as unknown-not-safe.
  • _LIVE_GOAL_STATUS_NAMES (deadman_watchdog_node.py L73) — ("STATUS_ACCEPTED", "STATUS_EXECUTING", "STATUS_CANCELING"); resolved to values off the generated message class rather than hardcoded, so an upstream renumbering cannot silently widen or narrow the gate.
  • DeadmanWatchdogNode.on_configure(state) -> TransitionCallbackReturn (deadman_watchdog_node.py L162) — Opens the /openral/safe_action subscription, the estop/failure publishers (QoS from _qos.estop_qos/_qos.failure_qos), and (if arm_status_topic is set) the goal-status subscription.
  • DeadmanWatchdogNode.on_activate(state) -> TransitionCallbackReturn (deadman_watchdog_node.py L228) — Arms the deadline check; opens the execution window immediately when free-running, else waits for the first live goal status.
  • DeadmanWatchdogNode.on_deactivate(state) -> TransitionCallbackReturn (deadman_watchdog_node.py L242) — Disarms — deadline checks become no-ops until the next activate.
  • DeadmanWatchdogNode.on_cleanup(state) -> TransitionCallbackReturn (deadman_watchdog_node.py L248) — Releases the timer, subscriptions and publishers.
  • DeadmanWatchdogNode.on_shutdown(state) -> TransitionCallbackReturn (deadman_watchdog_node.py L266) — Forces cleanup (delegates to on_cleanup).
  • main(args=None) -> int (deadman_watchdog_node.py L396) — Entry point for ros2 run openral_safety_watchdog deadman_watchdog_node.
  • class HardwareEstopNode(LifecycleNode) (hardware_estop_node.py L48) — Polls a hardware E-stop source and publishes /openral/estop plus a FailureTrigger on the rising edge. With no device declared, on_configure refuses to activate rather than coming up ACTIVE over a stub; a read that raises latches as unknown-therefore-unsafe.
  • DEFAULT_POLL_RATE_HZ (hardware_estop_node.py L44) — 100.0; how often to query the hardware estop state.
  • HardwareEstopNode.on_configure(state) -> TransitionCallbackReturn (hardware_estop_node.py L97) — Opens publishers and starts the polling timer only if a device is really there; returns FAILURE (leaving the node UNCONFIGURED) when this host has no hardware E-stop this node could read.
  • HardwareEstopNode.on_activate(state) -> TransitionCallbackReturn (hardware_estop_node.py L130) — Starts braking; a button already held at activation brakes on the first poll.
  • HardwareEstopNode.on_deactivate(state) -> TransitionCallbackReturn (hardware_estop_node.py L138) — Stops braking; the timer keeps running but _poll is a no-op.
  • HardwareEstopNode.on_cleanup(state) -> TransitionCallbackReturn (hardware_estop_node.py L144) — Releases the timer + publishers.
  • HardwareEstopNode.on_shutdown(state) -> TransitionCallbackReturn (hardware_estop_node.py L158) — Forces cleanup (delegates to on_cleanup).
  • main(args=None) -> int (hardware_estop_node.py L261) — Entry point for ros2 run openral_safety_watchdog hardware_estop_node.

packages/openral_rskill_ros/openral_rskill_ros/

  • RskillRunnerNode(*, node_name="openral_skill_runner", robot_description, aggregator, skill_resolver=None) (rskill_runner_node.py L244) — The ExecuteRskill action server: a lifecycle node dispatching to the resolved rSkill at rate_hz (param 0 = the manifest's action_spec.control_freq_hz, else 30 Hz — issue #303), aborting any blocking wait the instant /openral/estop or a latched /openral/safety_status reports unsafe. Every terminal outcome stamps both a prose failure_reason and a typed failure_kind. on_configure draws a random nonzero 64-bit _runner_session_id (logged as rskill_runner session id 0x…) that ROSPublishingHAL stamps on every ActionChunk; the tick counter restarts per process, so the HAL's replay watermark is keyed on (session, tick) (hazard log Entry 036 Amendment 3). Goal-scoped declarations: the goal's own place_declaration / grasp_declaration, else the scene's place_declaration_json / grasp_declaration_json param (for the grasp one, else with grasp_approach_enabled a goal-scope declaration: target_id="approach", every hand's links via openral_core.gripper_hands, no search box, timeout_s = the goal deadline capped at 120 s — the HAL's approach-armed target narrows it to one hand; for the place one, else with place_approach_enabled a goal-scope declaration: target_id="surface", no object, no search box, timeout_s = the goal deadline capped at 120 s — the HAL's place-target leg attaches the surface it measures under the carried payload and never declares on its own), is armed at goal start on /openral/place_declaration / /openral/grasp_declaration (RELIABLE + TRANSIENT_LOCAL, depth 1) with rskill_id / trace_id / stamp_ns overwritten and any region stripped, and retracted on every exit incl. E-stop (_arm_*_declaration / _retract_*_declaration). The goal-end publishes (the Episode END marker, the declaration retractions) go through _publish_unless_shut_down: once SIGINT has shut the rclpy context down a publish that raises InvalidHandle/RCLError is skipped with one rskill_runner.publish_skipped_context_shutdown info line (no traceback out of the action server, no …_retracted line for a retraction that never reached the wire); with the context up the error is unchanged and every retraction is still attempted in the same order. A supplied place declaration (goal or place_declaration_json) that does not validate aborts the goal with a ROSConfigError reason before the skill is resolved — never a fall-back to the goal-scope one.
  • _new_execution_budget(budget_s) -> ExecutionBudget / _deadline_lapsed(budget, chunks) -> bool — The goal's execution budget (node_execution_budget(self, …)) runs on the graph clock under use_sim_time (sim seconds, like Nav2's and MoveIt's own timeouts; re-read per goal) and on time.monotonic() otherwise; _deadline_lapsed logs the miss, adds the deadline_missed span event with miss.kind, and stashes the BudgetMiss so the aborted goal's failure_reason reads deadline_exceeded: … or clock_stalled: … (both FAILURE_DEADLINE_MISSED). ROS params execution_wall_cap_factor (20) and graph_clock_stall_s (10 s) are the wall-clock backstops, shared with every wrapped skill on the node and validated at on_configure (non-positive / non-finite fails the transition); a NaN goal deadline_s is rejected as FAILURE_CONFIG_ERROR. Adapted from DsslRobot/openral.
  • _failure_kind_for_exception(exc) -> int (module-level) — Maps the OpenRAL exception hierarchy onto the ExecuteRskill.Result.FAILURE_* uint8 constants, most-specific-first. A ROSSafetyViolation other than ROSEStopRequested has no kind by design — it is re-raised to the safety supervisor, never folded into a Result.
  • _classify_runtime_failure(exc) -> tuple[str, int] (staticmethod) — One message probe yielding both the typed failure_reason and the matching failure_kind for a raw non-ROSError escape, so the string and the uint8 can never disagree. _label_runtime_failure(exc) -> str is the thin projection onto the first element.
  • _apply_starting_pose(skill, goal_handle) -> tuple[int, str] | None / _interpolate_starting_pose(pose, goal_handle) -> tuple[int, str] | None / _dispatch_moveit_approach(pose, goal_handle) -> tuple[int, str] | None — Reach a manifest starting_pose before inference via either a configured MoveIt plan or a bounded joint ramp, checking cancellation between waypoints and failing the goal on any error.
  • make_default_skill_resolver(ros_node, *, search_paths=(), scene_cameras=(), tf_lookup=None, tf_lookup_getter=None) -> SkillResolver (rskill_runner_node.py L2668) — Production resolver factory: routes kind: vla to the local/HF-Hub path and ros_action/ros_service skills to ROSActionRskill. The Hub fallback _default_skill_resolver(..., scene_cameras=(), tf_lookup=None) now binds rSkill.from_pretrained's packaging handle to a runtime skill via _build_runtime_skill_from_manifest(handle.local_dir / "rskill.yaml", …) — until 2026-09-22 it returned the handle raw, so every Hub-hosted VLA aborted at the embodiment gate (which now raises a typed ROSConfigError naming a non-rSkillBase return instead of a bare AttributeError with an empty failure_reason). Preload: parameters preload_rskill_id / preload_rskill_revision / preload_prompt make on_activate start a daemon worker (_preload_resident_skill) that runs _acquire_skill under _execute_serial so the skill is GPU-resident before any goal exists (a deactivate/activate cycle mid-load keeps the one worker: on_activate never starts a second while the first is alive, so _preload_in_flight stays set until the load finishes); _goal_cb REJECTS goals while _preload_in_flight is set (acceptance is what arms the deadman's first-chunk window), and REJECTS every goal while the e-stop latch is held, logging rskill_runner.goal_rejected: e-stop latched at WARNING with the recovery (openral estop reset) since a ROS 2 rejection carries no reason, and _acquire_skill keeps the weights resident per (id, revision): a skill exposing set_goal_prompt(prompt) (the VLA shim from _make_policy_adapter_skill, which applies _effective_goal_prompt's default_prompt fallback and resets the adapter so the previous goal's buffered chunk / prefetch is dropped) is retargeted to the new goal's prompt with no reload, so preload_prompt is only a warm-up prompt; any other skill keeps (prompt, prompt_metadata_json, goal_params_json) in its key, because a wrapped-ROS skill lowers them into its goal at construction. A key change logs rskill_runner.resident_key_mismatch, since it costs the full cold load inside the watchdog window. A ROSSafetyViolation raised while preloading is logged as rskill_runner.preload_safety_violation and handed to _rethrow_on_executor(exc), a one-shot timer that re-raises it on the executor thread so it escapes spin() exactly as it does from the goal path (a bare re-raise on the worker thread would end only that thread while the node kept accepting goals).
  • make_local_skill_resolver(search_paths, *, scene_cameras=(), tf_lookup=None, tf_lookup_getter=None) -> SkillResolver (rskill_runner_node.py L2825) — In-tree VLA resolver. Walks each search path once and indexes every */rskill.yaml by manifest name; on resolve calls _build_runtime_skill_from_manifest. Accepts ros_node=None kwarg for signature uniformity with the default resolver.
  • resolve_control_rate_hz(param_hz, description) -> float | None (rskill_runner_node.py L2930) — The tick rate: rate_hz > 0 wins, else the manifest's action_spec.control_freq_hz, else None; RskillRunnerNode._control_rate_hz() then warns (naming the missing field) and ticks at 30 Hz, which only a sim-only manifest can reach because a real ros2_control HAL refuses to build without the field. Issue #303: the real ros2_control HAL derives every trajectory deadline from that same manifest field, so runner and HAL cannot disagree unless an operator overrides the param on purpose. RskillRunnerNode._control_rate_hz() wraps it for both tick loops.
  • starting_pose_joint_bounds(param_max_delta_per_s, param_tolerance_rad, description) -> list[tuple[float, float]] (rskill_runner_node.py L2955) — Per-joint (max speed, arrival tolerance) for the move to a skill's starting pose, each in the joint's own units: prismatic joints use safety.starting_pose_max_joint_speed_m_s / starting_pose_tolerance_m, every other joint the _rad_s / _rad fields; each speed is capped at the joint's velocity_limit. Declared per robot, never derived (issue #303). A positive param overrides the rad values only, never a prismatic joint. ROSConfigError naming the missing field. Wrapped by RskillRunnerNode._starting_pose_bounds(); replaced the scalar resolve_starting_pose_ramp (audit C F2: a 0.05 "rad" tolerance swallowed a 0.041 m gripper stroke and the Franka gripper ramped at 5x its limit).
  • starting_pose_reached(current, target, bounds) -> bool (rskill_runner_node.py L3011) — Every joint within its own tolerance; the runner's arrival check before the first inference tick.
  • starting_pose_ramp_steps(current, target, bounds, rate_hz) -> int (rskill_runner_node.py L3026) — Ticks of the linear ramp: 0 when already reached, else the longest per-joint |delta| / speed * rate_hz, so no joint exceeds its own cap.
  • resolve_control_rate_source(param_hz, description) -> tuple[float, str] (rskill_runner_node.py L3054) — The tick rate and its source (rate_hz_param / manifest / fallback = _FALLBACK_CONTROL_RATE_HZ, 30 Hz). _control_rate_hz() warns on fallback; _check_skill_control_rate records control.rate_hz / control.rate_source on the rskill.execute span.
  • check_skill_control_rate(trained_hz, tick_hz, *, skill_id, robot) -> None (rskill_runner_node.py L3075) — ROSConfigError when the skill's action_contract.control_freq_hz is declared and differs from the tick rate (there is no action resampler); the goal is rejected before the starting-pose move. None = undeclared, unchecked.
  • _require_positive_param(node, name) -> float (rskill_runner_node.py L3102) — Reads a float ROS param the launch must set (joint_state_staleness_limit_s, declared 0.0 = unset) and raises ROSConfigError naming it when it is not positive; deploy_e2e.launch.py sets it from the manifest's safety.joint_state_staleness_limit_s in real mode (_runner_joint_state_staleness_s(description, hal_mode, *, republished); when the runner reads the HAL's rate-limited ~/joint_states republish it adds two republish periods, limit + 2 / control_freq_hz, capped at the former 0.5 s — the HAL's own check stays at the manifest value; sim keeps 0.5 s).
  • _warmup_observation() / shim on_warmup (rskill_runner_node.py) — Warm-up through the real tick path: the policy shim's on_warmup runs adapter.step on _warmup_observation() — zeros shaped like the cell's RGB intrinsics for every required camera slot, state_contract.dim zeros (falling back to the robot description's joint count; with neither it raises a typed ROSConfigError, which the non-fatal wrapper logs as rskill_runner.warmup_failed and activate proceeds cold), the goal prompt — then adapter.reset(), instead of a bare warm_up_lerobot_policy forward. The bare forward left the first real tick paying 306–357 s on a Jetson AGX Orin under a live graph (chunk executor thread, autocast, preprocessor on real-sized frames each have first-call costs) against the deadman's 120 s first-chunk window; a warm second dispatch in the same process took 426 ms, which is what the preload now pays.
  • prop _CANCEL_DRAIN_S, _MAX_APPROACH_WAYPOINTS, _DEFAULT_EXECUTION_DEADLINE_S (rskill_runner_node.py L93–105) — The 100 ms cancel drain; the runaway guard on the MoveIt approach replay waypoint count; and the global fallback (45 s) for a goal's execution budget when neither the dispatch nor the skill manifest declares one.
  • prop _SAFETY_STATUS_TOPIC, _SAFETY_STATUS_LIVENESS_S (rskill_runner_node.py L115–125) — ADR-0096's latched safety-state topic (/openral/safety_status) both the C++ kernel and SafetyPassthroughNode publish on, and the HZ-0096-1 liveness window (3 s) past which a stale SafetyStatus is reported as an abort reason rather than downgraded to clear.
  • main(args=None) -> int (rskill_runner_node.py L4074) — Entry point for ros2 run openral_rskill_ros rskill_runner_node; bootstraps a standalone node with a fresh RobotDescription and WorldStateAggregator. Production launches use compose_so100_runtime instead, to share the aggregator with the colocated world_state_node.
  • compose_runtime(robot_yaml, *, skill_resolver=None, enable_world_cloud_bridge=False, world_cloud_topic="", slam_source_node="", dataset_out=None, image_staleness_limit_s=None, deploy_sensors=(), sensor_overlays=(), …) -> ComposedRuntime (compose.py L179) — Loads a robot manifest and builds one WorldStateAggregator shared by a colocated world-state node and RskillRunnerNode, always attaching a SlamMapBridge so any /map publisher populates the dashboard. Cameras get their own freshness window, independent of joint/EE staleness. deploy_sensors (a DeployScene.sensors block) is merged into the manifest's sensors via merge_deploy_sensors before the aggregator and runner are built, so a scene's workcell cameras (and any vla_feature_key they carry; the scene never touches a robot camera) reach the runner's camera slots, the dataset recorder and world state alike — until 2026-09-23 only the sensor readers saw it and the OpenArm pick-and-place π0.5 ran with its context (ZED head) view replaced by lerobot's masked blank. sensor_overlays (the host's RobotUnit overlays, resolved by runtime_node) are applied to the manifest sensors first (apply_sensor_overlays).
  • spin_until_shutdown(executor) -> None (compose.py L86) — executor.spin() until a signal-driven shutdown: KeyboardInterrupt, ExternalShutdownException and a RuntimeError (covers RCLError / InvalidHandle, and a take racing the shutdown) raised after the context went down (the SIGINT handler invalidates it mid-spin: the next wait set, a dequeued callback's publish, a subscription take) all end it quietly; a RuntimeError with the context still up propagates. runtime_node and the world-state events thread spin through it (the runtime exited 1 on that race). The executor twin of openral_hal.lifecycle.spin_until_shutdown, which spins a node; kept per layer (the rSkill layer does not import the HAL).
  • start_world_state_executor(world_state_node, *, on_failure=None) -> Callable[[], None] | None (compose.py L112) — ExternalShutdownException (Ctrl-C, or an external rclpy.shutdown()) is a clean exit, never a failure. A callback that raises ends spin on the daemon thread; the exception is logged as world_state.executor_died and handed to on_failure (the runtime_node script stops its own executor and exits 7), or re-raised when no handler is given; the stop callable's own shutdown is not a failure. spins the world-state node on rclpy's C++ rclpy.experimental.EventsExecutor in a daemon thread and returns a stop callable (shutdown + join), or None on an rclpy without it (pre-Jazzy 7.1) so the caller adds the node to its own executor. Why: the Python MultiThreadedExecutor rebuilds its wait set in Python on every wake-up over every entity of every node it holds; with world_state's ~40 topics on it that loop held 64 % of the deploy runtime's GIL on an AGX Orin and the in-process π0.5 inference thread got <2 % (1.6 s idle forward → ~300 s live). The skill runner stays on the multi-threaded executor, whose reentrant group keeps execute_rskill accept/cancel/result live while a goal blocks a worker.
  • compose_so100_runtime(*, skill_resolver=None) -> ComposedRuntime (compose.py L411) — SO-100 convenience wrapper over compose_runtime.
  • ComposedRuntime (compose.py L41) — Dataclass returned by compose_runtime: description, aggregator, world_state_node, skill_runner_node, slam_bridge, world_cloud_bridge, dataset_recorder_bridge fields.
  • joint_names_from_goal_json(default_goal_json) -> list[str] (_starting_pose.py L10) — Extracts the planning-group joint names from a MoveGroup default_goal_json's joint.joint_names block; used to length-check a robot's flat starting_pose before building the retarget override. Raises ValueError on malformed JSON or a missing/empty joint.joint_names.
  • moveit_joint_goal_override(joint_names, positions) -> str (_starting_pose.py L47) — Builds the goal_params_json that retargets the approach manifest's joint.positions to positions (deep-merge over the manifest, so joint_names's order is preserved); joint_names is used only to length-check positions.
  • SensorLeg (sensor_leg.py L199) — Real-mode camera leg for openral deploy run, the real-hardware counterpart of the sim HAL's SimSensorBridge. Dataclass holding readers + publishers + which sensors write directly to the aggregator without a duplicate ROS-topic path.
  • SensorLeg.start() -> None (sensor_leg.py L219) — Starts every prepared publisher after ROS lifecycle configuration; entity creation and lifecycle transitions remain single-threaded until this point.
  • SensorLeg.close() -> None (sensor_leg.py L232) — Stops publishers first, then closes readers, idempotently and exception-safe (teardown must always reach the HAL shutdown behind it).
  • _DEFAULT_PUBLISH_RATE_HZ (sensor_leg.py L61) — 10.0; publish cadence when the binding's backend_params carry no fps. Matches the WorldStateAggregator staleness-gate expectation.
  • _RGB_CHANNELS (sensor_leg.py L65) — 3; channel count of an interleaved colour frame — the only layout the 180° dashboard flip knows how to reshape.
  • _MAX_FALLBACK_TOPIC_RATE_HZ (sensor_leg.py L85) — Cap on the ROS topic cadence for the Python fallback publisher only (the native GStreamer tee is uncapped).
  • _DEFAULT_TOPIC_MAX_SIZE (sensor_leg.py L96) — (320, 240); resolution ceiling topic_frame_size falls back to.
  • slam_camera_names(runtime) -> frozenset[str] (sensor_leg.py L287) — Camera names feeding visual SLAM, which must never be rate-capped (cuVSLAM / PyCuVSLAM lose the track on a starved stream). Empty when SLAM is off, in which case the fallback-rate cap applies to every camera.
  • open_deploy_sensor_readers(sensors, *, aggregator=None, ros_node=None, uncapped_sensors=(), topic_max_size=None) -> SensorLeg (sensor_leg.py L565) — Builds the complete deploy-bound sensor batch; every camera publishes on openral_core.camera_topic(name) (+ CAMERA_INFO with intrinsics; the topic_prefix argument and DEFAULT_TOPIC_PREFIX were removed by ADR-0108) — gstreamer backends via an in-pipeline ROS tee, others via a polling publisher. On both paths a spec with intrinsics also publishes CameraInfo stamped with spec.frame_id (the tee gets them via SensorReaderConfig.publish_frame_id / publish_camera_info). A shared aggregator also gets a pump writing frames directly into WorldStateAggregator; the fallback publisher's max_size comes from _topic_max_size (capture size for cameras in uncapped_sensors or with topic_native: true, else topic_max_size).
  • _AggregatorPump (sensor_leg.py L123) — Daemon-thread pump polling read_latest at the spec rate into the aggregator, deduplicating on the frame's monotonic stamp. Emits the per-frame observability span after each write, guarded so a failing display path can never starve the policy.
  • _emit_frame_observability(sensor_name, frame, flip_180) -> None (sensor_leg.py L99) — Emits the dashboard's per-frame observability span for a pump-fed camera; exists because the ROS tee these cameras skip is what the dashboard would otherwise read from.
  • _sensor_topics(spec) -> tuple[str, str] (sensor_leg.py L253) — (image, camera_info) topics by modality: a depth sensor → camera_topic(name, DEPTH_IMAGE / DEPTH_CAMERA_INFO) (where the sim bridge and every depth consumer look), anything else → IMAGE / CAMERA_INFO.
  • _publish_rate_hz(spec) -> float (sensor_leg.py L276) — Binding fps, else spec rate_hz, else 10 Hz. This is the capture/native-tee cadence and is not capped.
  • _fallback_topic_rate_hz(spec, uncapped=()) -> float (sensor_leg.py L413) — Caps the ROS topic cadence for the Python fallback publisher only (at _MAX_FALLBACK_TOPIC_RATE_HZ, 3 Hz; an explicit positive topic rate wins and a camera in uncapped — the SLAM cameras — keeps its full rate), since every tick is a GIL-held full-resolution conversion; the native GStreamer tee and the policy's in-process read stay at full rate.
  • _topic_native(spec) -> bool (sensor_leg.py L447) — backend_params["topic_native"] is True: that camera's fallback topic publishes at capture size (and its CameraInfo unscaled) regardless of topic_frame_size, for a bus reader that needs exact pixels (Foxglove, openral record, calibration). Resolution only — the 3 Hz rate cap still applies unless topic_rate_hz lifts it; a native 960x600 publish is ~55 ms of GIL per frame per camera, so it is per camera and opt-in. Only a boolean true counts (YAML 1.1 also reads unquoted yes/on as one); any other value keeps the ceiling and logs sensor_leg.topic_native_ignored. Off by default in every unit file, the OpenArm's included (a commented switch on each wrist for calibration passes). The in-process paths (the policy's aggregator read, --dataset-out) are native regardless.
  • _topic_max_size(spec, uncapped, ceiling) -> tuple[int, int] | None (sensor_leg.py L490) — Resolution ceiling for one camera's fallback topic, the per-camera counterpart of _fallback_topic_rate_hz: None (capture size) for a SLAM camera in uncapped or a topic_native: true binding, else the launch-wide ceiling from topic_frame_size. What open_deploy_sensor_readers hands SensorRosPublisher.max_size.
  • topic_frame_size(runtime) -> tuple[int, int] | None (sensor_leg.py L381) — Resolution ceiling for the fallback topic; None (native pixels) when the object detector or SLAM is on, since both need full resolution. The policy is unaffected either way, reading the aggregator in-process at capture resolution; a single camera opts out of the ceiling with topic_native: true (_topic_native).
  • apply_launch_overrides(runtime, *, enable_object_detector=None, enable_slam=None, slam_stereo_cameras=None, slam_mono_camera=None) -> object | None (sensor_leg.py L323) — Folds the launch's resolved auto-detected flags over the scene's raw runtime block before camera rate-capping decisions are made, since only the CLI resolves the scene's tri-state auto settings.

packages/openral_rskill_ros/scripts/runtime_node

Composed-runtime entry point installed as lib/openral_rskill_ros/runtime_node. Reads the robot_yaml parameter, calls compose_runtime, and spins both lifecycle nodes on a MultiThreadedExecutor; for a real deploy it also opens every deploy-bound sensor reader before spinning. The script eagerly initializes GStreamer before any numpy/pydantic/rclpy import, since a later rclpy.Node() can segfault otherwise. Spins the world-state node on the C++ EventsExecutor (start_world_state_executor; falls back to sharing the multi-threaded executor with a stderr notice) and the skill runner on a 4-thread executor. Launch param joint_states_topic (resolved by deploy_e2e.launch.py via openral_hal.hal_joint_states_topic: scene override, else a real ros2_control HAL's /<hal_node>/joint_states) reaches both composed nodes through the launch's wildcard params: world_state's ingest and the runner's ROSPublishingHAL joint-state cache (RskillRunnerNode param joint_states_topic, "" = /joint_states) share the one topic, so a ros2_control arm's 750 Hz broadcaster stream never wakes the Python executors. With a deploy_config, it loads the scene BEFORE compose_runtime and passes its sensors: as deploy_sensors; the sensor leg then reads readers off that merged description.

  • _prewarm_vla_imports() -> None — Imports torch + lerobot.policies.factory early so the shared bulk of any VLA adapter's import cost is paid before any camera-reader thread exists. Must run ahead of open_deploy_sensor_readers, since a transformers import can otherwise starve a reader thread of the GIL for minutes.

packages/openral_rskill_ros/launch/deploy_e2e.launch.py

  • compose_runtime_graph(context, *_args, **_kwargs) -> list (L1673) — Resolves every launch arg, loads the robot manifest, and assembles the full deploy-sim ROS graph — HAL, safety kernel, reasoner, SLAM/Nav2, sensor drivers, optional Foxglove viz. On hal_mode:=real it also starts the robot's vendor ros2_control bringup itself. Always passes the kernel grasp_allowance_enabled (launch arg, default false, from DeployRuntime.grasp_allowance_enabled) and grasp_contact_links = the manifest's role: gripper child_links (omitted when empty); grasp_allowance_enabled:=true on a robot with no gripper joint raises ROSConfigError, and so does it on any non-sim launch unless enable_vision_attachment:=true and the HAL's hal_params_file (read back by _hal_file_params) sets vision_attachment_enabled and vision_attachment_grasp_target_enabled true (no region producer on real otherwise). With workspace_contact_enabled:=true (issue #349; plus workspace_contact_max_speed_mps:= and workspace_contact_max_lead_m:=) it passes the kernel _workspace_contact_kernel_params(...): workspace_contact_links = RobotDescription.hand_links(), workspace_contact_jaw_joints = the role: gripper joint names, workspace_contact_brake_enabled: true, the speed and lead bounds, and workspace_contact_control_period_s = 1 / action_spec.control_freq_hz; refused (ROSConfigError) on a non-sim hal_mode, without grasp_allowance_enabled, without a positive speed bound, without a lead bound in (0, 0.10] m, or for a robot with no gripper / no control rate; off, it passes workspace_contact_enabled: false. It also passes grasp_region_max_age_s and place_region_max_age_s = 2 × world_voxel_deadline_s (how old a producer-measured region may be and still exempt anything). The octomap voxel bridge gets attach_link_tf_frames from the vision_attachment_tf_frames launch arg (comma-joined link=frame, forwarded by resolve_launch_invocation with the leg on as the exact strings the HAL gets) so a held payload's manifest attach link is found on the cell's TF tree; omitted when empty. The reasoner gets grasp_target_voxel_m = _octomap_resolution(hal_mode) (one cell of the map the grasp-target producer searches). With the vision leg on, the HAL node also gets vision_attachment_grid_max_age_s = the kernel's world_voxel_deadline_s and vision_attachment_release_clear_m = _world_voxel_margin_m(hal_mode) + _octomap_resolution(hal_mode) (one octree cell), placed before the CLI's params file so an explicit --hal value still wins; whatever the source, a vision_attachment_grid_max_age_s above the kernel's world_voxel_deadline_s or a vision_attachment_release_clear_m below world_voxel_margin_m + resolution fails the launch with ROSConfigError (checked whenever the launch arg or the HAL params file enables the leg). The robot self-filter (in front of octomap) runs wherever the input cloud shows the robot, _cloud_shows_the_robot(hal_mode, scene_backend): real cameras and an isaacsim deploy scene (its depth camera renders the arm), not the MuJoCo sensor bridge (robot bodies transparent). It needs no rename: it poses a manifest attach link from its own collision model (link_index) and only falls back to TF for a non-model link.
  • _static_points_file(static_geometry_file, resolution_m) -> str (L318) — Samples the declared static geometry a quarter cell apart (openral_safety.static_geometry) into a temp points file for the octomap bridge's static_points_file; the launch refuses static_geometry_file with enable_octomap off (declared walls never dropped silently). (#349)
  • generate_launch_description() -> LaunchDescription (L3929) — Robot-agnostic deploy-sim launch graph entry point; wraps compose_runtime_graph in an OpaqueFunction.
  • _foxglove_asset_env(robot_description_xml) -> dict[str, str] (L1326) — foxglove_bridge's additional_env: prepends openral_hal.ros_package_overlay.public_package_overlay to AMENT_PREFIX_PATH so a viewer's fetchAsset resolves package:// meshes of a fetched public package (OpenArm's openarm_description); {} when none is needed or the overlay cannot be built (printed, never raised).
  • _build_real_bringup_include(real_bringup) -> object | None (L1358) — IncludeLaunchDescription of the manifest's hal.real_bringup ("<pkg>:<file>.launch.py") on hal_mode:=real; None when the manifest declares none; raises RuntimeError when the declared package or file is not installed. There is no package-name convention fallback.
  • _VENV_SITE (L44) — Optional workspace-editable-install site-dir from OPENRAL_VENV_SITE, registered via site.addsitedir (plain PYTHONPATH is not enough: .pth files are only processed by the site module on registered site-dirs).
  • _REPO_ROOT (L130) — Resolved repo root (_resolve_repo_root()).
  • _RSKILLS_DIR (L131) — str(_REPO_ROOT / "rskills").
  • _VENV_RAL (L133) — _REPO_ROOT / ".venv" / "bin" / "openral".
  • _RAL_EXECUTABLE (L134) — The workspace venv's openral binary when it exists, else the bare "openral" on PATH.
  • _stereo_camera_topics(names_csv) -> tuple[str, str, str, str] | None — "<left>,<right>" → the four camera_topic(...) stereo topics; None when not exactly two names (the stereo SLAM impl then gets no topics and refuses to start, ADR-0108).
  • _primary_rgb_camera(sensors) -> str — Optical-framed RGB sensor in sensors (the deploy's publishing_sensors) first, else the first RGB sensor, else ""; the object detector's and the reasoner completion camera's view.
  • _live_camera_info_topic(spec, hal_mode) -> str — The CameraInfo carrying a camera's live calibration: sim → camera_topic(name, CAMERA_INFO); real → openral_sensors.ros_publisher.camera_info_topic_for(<ros2_image binding topic>) (the driver's, never the sensor leg's manifest-built one); any other real backend → "". Feeds the detector's and locators' camera_infos.
  • _depth_camera(description) -> str — The manifest's first depth sensor with intrinsics (openral_hal.depth_cloud.is_depth_sensor), else ""; feeds nvblox under enable_nav2.
  • _build_estimated_depth_nodes(camera, *, rgb_camera_info_topic, rgb_image_rectified, cloud_frame, optical_parent, use_sim_time) -> list (L771) — DeployRuntime.estimated_depth_camera's DA3METRIC sidecar (_ESTIMATED_DEPTH_MODEL on _ESTIMATED_DEPTH_PORT 5772, beside mono SLAM's 5771) + depth_provider_node in metric mode on that camera's canonical image / depth / points topics, its focal and lens from rgb_camera_info_topic (_live_camera_info_topic: the driver's own calibrated CameraInfo on a real deploy, else the sensor leg's, built from this unit's calibrated intrinsics); rgb_image_rectified (the binding's backend_params.rectified) reads an already-rectified stream through CameraInfo.p. Stamps everything in cloud_frame (depth_frame_id) and publishes optical_parent -> cloud_frame (REP-103) on /tf_static for a body-convention mount.
  • _cloud_shows_the_robot(hal_mode, scene_backend, *, estimated_depth=False) -> bool (L742) — Whether octomap's input cloud contains the robot (real, an Isaac render, or a DA3 estimate), so the robot self filter runs on it.
  • _octomap_cloud_topic(pinned, description, hal_mode, estimated_camera="") -> str — openral_core.deploy_cloud_topic, raising ROSConfigError when it is empty (a real deploy with nothing pinned, or no depth sensor) so a direct ros2 launch never spawns octomap_server against silence. The object-lift fallback (object_depth_points_topic) takes deploy_cloud_topic unchecked ("" disables it).
  • _rig_from_launch_args(raw) -> DeployRuntime (L414) — The per-rig DeployRuntime values openral deploy forwards as launch args (_RIG_LAUNCH_ARGS: world_voxel_deadline_s, max_octree_age_s, world_voxel_data_age_budget_s, robot_self_filter_padding_m; empty = schema default), validated through DeployRuntime, which refuses an octree age above the deadline (hazard log Entry 033). compose_runtime_graph feeds voxel_freshness_s to the kernel's world_voxel_deadline_ms and the octomap bridge's max_octree_age_s, and world_voxel_data_age_budget_s to the kernel's world_voxel_data_age_budget_ms (Entry 034).
  • _SELF_FILTERED_CLOUD_TOPIC (L438) — /openral/world_cloud/self_filtered, octomap_server's input on the real path; deliberately not a /openral/cameras/... topic (ADR-0108), since it is the world map's input rather than one camera's cloud; x/y/z only (the self filter strips any other field).
  • _SELF_FILTERED_RGB_CLOUD_TOPIC — /openral/world_cloud/self_filtered_rgb, the same robot-free cloud with every input field (a sim cloud's rgb), published by the self filter only while subscribed; for viewers (Foxglove), never the map.
  • _self_filter_params(collision_params, description, joint_states_topic, padding_m) -> dict[str, object] (L445) — The robot_self_filter node's parameters: every collision_* key the kernel receives (so both pose the same model), collision_joint_names from the manifest with each joint's sim_joint_name as collision_joint_aliases, the runtime nodes' joint-state topic (hal_joint_states_topic: scene override, else a real ros2_control HAL's ~/joint_states, else /joint_states) and padding_m (the rig's DeployRuntime.robot_self_filter_padding_m, default 0.02 m, provisional; it is also the width of the blind shell around the arm). Wired only on hal_mode == "real" with a collision model; octomap_server's cloud_in is then remapped to the filtered topic.
  • _hal_file_params(path, node_name) -> dict[str, object] (L879) — The ros__parameters the HAL's hal_params_file gives it (/**, then its own node name), so the launch's HAL couplings judge the effective params whatever wrote the file; a missing/unreadable file reads as {} (launch_ros refuses it when the HAL starts, and an absent key reads as off).
  • _attached_collision_enabled(hal_mode, vision_attachment_enabled) -> bool (L857) — Whether the kernel gets attached_collision_enabled: hal_mode == "sim" (the sim attachment manager) or the vision attachment leg is on (enable_vision_attachment:=true, from DeployRuntime.vision_attachment). Coupling rule: the vision leg on real always turns the kernel's attached check on, never the leg alone — the octomap bridge's payload clearing and the robot self-filter consume /openral/world_state_fast regardless of the flag. The leg on a robot with no collision geometry raises ROSConfigError. With the leg on, the launch also composes the openral_segmenter lifecycle node (segmenter_node.py: robot_yaml, manifest_path, cameras/camera_infos = <camera>=<rgb topic> / <camera>=<rgb camera_info>, primary_camera, device, use_sim_time), autostarted to ACTIVE.
  • _attached_collision_deadline_ms(hal_mode) -> float (L870) — The kernel's attached_collision_deadline_ms: 5000 ms on sim, 1000 ms on real.
  • _cpuset_prefix(env) -> str (L196) — A taskset -c launch prefix from OPENRAL_PERCEPTION_CPUSET (octomap_server + the voxel bridge) or OPENRAL_RUNTIME_CPUSET (the runtime node), or ""; unset = nothing pinned. Affinity needs no privilege where raising priority does (ulimit -e 0 on Thor); a value that is not a cpu list is refused loudly.

python/runner/src/openral_runner/ros_publishing_hal.py

HAL Protocol adapter that publishes ActionChunk on /openral/candidate_action.

  • ROSPublishingHAL(*, node, description, skill_id_getter=..., skill_revision_getter=..., tick_index_getter=lambda: 0, runner_session_id=0, joint_state_topic="/joint_states", candidate_action_topic="/openral/candidate_action", action_applied_topic="/openral/action_applied", safety_abort_getter=lambda: None, action_applied_timeout_s=5.0, joint_state_staleness_limit_s=0.5) (L117) — Publishes typed candidate action chunks and blocks with bounded backpressure until the HAL acknowledges application, polling the injected safety latch every 50 ms and raising ROSEStopRequested instead of hiding a stop behind a plain timeout. Stamps runner_session_id (the runner's random per-session uint64; 0 = session-less) on every ActionChunk, so the HAL keys its replay watermark on (session, tick).
  • _row_major_flatten(rows) -> list[float] (L57) — Private helper used by _action_to_chunk; preserves row-major ordering for the chunk flat array.
  • _CONTROL_MODE_TO_UINT8: dict[ControlMode, int] (L114) — Stable mapping from openral_core.ControlMode to the uint8 slot in ActionChunk.control_mode.
  • ROSPublishingHAL.connect() -> None (L218) — Opens the publisher on candidate_action + subscriber on joint_states.
  • ROSPublishingHAL.disconnect() -> None (L271) — Closes the publisher + subscription. Idempotent.
  • ROSPublishingHAL.read_state() -> JointState (L288) — Returns the latest cached JointState from /joint_states; raises ROSRuntimeError if called before connect() or before any message has arrived.
  • ROSPublishingHAL.send_action(action) -> None (L304) — Publishes an ActionChunk on /openral/candidate_action. Does NOT touch motors — the safety node republishes the chunk on /openral/safe_action, which the per-robot HAL lifecycle node consumes and forwards to the underlying controller.
  • ROSPublishingHAL.begin_goal() -> None (L319) — Resets goal-local partial-group state.
  • ROSPublishingHAL.estop() -> None (L394) — Triggers an emergency stop by raising ROSEStopRequested so the safety supervisor boundary can record and brake; the owning lifecycle node also publishes on /openral/estop as defense-in-depth.
  • _SAFETY_ABORT_POLL_S (L54) — 0.05; how often a blocked apply-wait polls the injected safety latch — a latched safety stop produces silence, nothing to wake the wait on, so this bounds how long the abort stays invisible.

python/runner/src/openral_runner/slam_bridge.py

rclpy → OTLP bridge for slam_toolbox /map. Throttles to 1 Hz, rasterises the nav_msgs/OccupancyGrid to a base64 PNG, looks up the robot's map-frame pose via tf2, and emits one slam.occupancy_grid OTel span the dashboard's SLAM Map card renders (store handler in openral_observability.dashboard.store).

  • SLAM_MAP_TOPIC_DEFAULT = "/map" (L43) — Default nav_msgs/OccupancyGrid topic slam_toolbox publishes on.
  • encode_occupancy_grid_png(*, width: int, height: int, data: list[int]) -> str (L70) — Pure function rendering an OccupancyGrid.data array as a base64 PNG (unknown→mid-grey, free→white, occupied→black, in-between linear ramp; flipped so map-north points up). Raises ValueError if len(data) != width * height. Exercised directly by tests against synthetic grids.
  • robot_pose_from_transform(*, translation_xyz: tuple[float, float, float], rotation_xyzw: tuple[float, float, float, float]) -> tuple[float, float, float] (L128) — Planar (x, y, yaw) from a tf2 transform's translation + rotation (z ignored); delegates yaw to openral_core.geometry.quat_xyzw_to_yaw. Used by SlamMapBridge to project the map→base_frame lookup into the span attributes.
  • class SlamMapBridge (L159) — rclpy.node.Node-hosted subscription on /map; on each accepted callback rasterises the grid, looks up the robot pose, and emits a slam.occupancy_grid span the dashboard renders. Degrades gracefully (no robot marker) when TF is unavailable.
  • __init__(node, *, topic=SLAM_MAP_TOPIC_DEFAULT, base_frame="base_link", footprint_radius_m=None, footprint_polygon=None, source_node_name="openral_slam_toolbox", publish_interval_s=1.0, max_cells=4_000_000) (L205) — Subscribes with slam_toolbox's QoS; footprint_polygon (base-frame XY) lets the dashboard draw the true oriented outline instead of falling back to the footprint_radius_m circle.
  • destroy() -> None (L277) — Release the ROS subscription. Safe to call multiple times.
  • _DEFAULT_PUBLISH_INTERVAL_S (L49) — 1.0; throttle so a busy SLAM run doesn't flood the OTLP pipeline (the dashboard re-renders on every span).
  • _UNKNOWN_GREY (L58) — 128; the 8-bit greyscale value an unknown (-1) occupancy cell renders as.
  • prop _OCC_FREE_THRESHOLD, _OCC_OCCUPIED_THRESHOLD (L61–62) — 0 / 100; the nav_msgs/OccupancyGrid probability bounds free/occupied map to (white/black), with values in between scaled linearly.
  • prop _OVERSIZE_WARN_BURST, _OVERSIZE_WARN_RATE (L66–67) — 3 / 100; how many oversize-grid warnings are emitted verbosely before falling back to a 1-in-100 rate to keep log noise bounded.

packages/openral_foxglove_bringup/

Read-only Foxglove live-scene surface, hybrid with the OTel dashboard which keeps traces, metrics, and every write path. Provides the bridge + republishers, a converter node, and an MCAP recorder; layout generated by python -m openral_foxglove_bringup.layout. Spawned into deploy-sim by openral deploy sim --foxglove.

  • SCENE_TOPICS: list[str] (topics.py L26) — Allowlist group 1: the geometry needed to draw the robot in its world — camera images + camera_info, /map, octomap cloud, /scan, /odom, /joint_states, /robot_description, /openral/robot_description, /tf(_static).
  • DEPTH_TOPICS: list[str] (topics.py L56) — Allowlist group 2: the RGBD / nvblox / visual-SLAM leg — per-camera depth + points, the self-filtered world cloud with its colour (/openral/world_cloud/self_filtered_rgb, the viewer's robot-free cloud; octomap's x/y/z-only input /openral/world_cloud/self_filtered is not exposed), the DA3 sidecar's depth topics, nvblox's filtered depth + ESDF cloud, /openral/nav2/scan, /openral/imu, /openral/visual_slam/odometry. Published only under the matching deploy posture.
  • BUCKET2_TOPICS: list[str] (topics.py L79) — Allowlist group 3: the bucket2_markers converter's outputs (/openral/world_voxels_cloud, /openral/viz/attachments, /openral/viz/static_geometry, /openral/viz/collisions).
  • TELEMETRY_TOPICS: list[str] (topics.py L106) — Allowlist group 4: the mission/state plane mirrored from the OTel dashboard's cards — world/policy state, episode, critic score, active task, objects, attachments, skill registry, diagnostics. Observation-only; /openral/safety_status is excluded pending safety-WG sign-off.
  • BUCKET1_TOPIC_WHITELIST: list[str] (topics.py L125) — The foxglove_bridge topic_whitelist: the four groups above concatenated, nothing else — notably never the safety/e-stop/action/command topics. Imported by both launch files so the allowlist has one source of truth.
  • ASSET_URI_ALLOWLIST: list[str] (topics.py L153) — The bridge's asset_uri_allowlist, controlling which mesh/asset paths a connected viewer may fetch to draw the URDF; extended from upstream's default to admit dotted version segments while still refusing path traversal. Applied by both launch files.
  • READ_ONLY_CAPABILITIES: list[str] (topics.py L158) — ["connectionGraph", "assets"]; omits clientPublish/services/parameters so a connected viewer cannot publish, call services, or write params (cannot actuate).
  • DEFAULT_CAMERAS: tuple[str, ...] (layout.py L57) — ("top", "wrist_left", "wrist_right"); the camera slots the shipped layout is generated for. These are sensor names from the robot manifest, not labels — a slot that doesn't exist on a given robot renders as a dead panel.
  • DEFAULT_LAYOUT_PATH: str (layout.py L60) — "config/openral_layout.json"; where the generated layout is shipped, relative to the package root.
  • camera_image_topic(camera, *, compressed=False) -> str (layout.py L63) — openral_core.camera_topic(camera), or the image_transport /compressed sibling.
  • ROBOT_DESCRIPTION_TOPIC: str (layout.py L89) — "/robot_description"; the std_msgs/String topic the layout's URDF layer reads (manifest URDF under deploy sim, vendor URDF under deploy run).
  • urdf_layer(topic=ROBOT_DESCRIPTION_TOPIC) -> dict[str, dict[str, Any]] (layout.py L92) — 3D-panel layers entry (layerId: "foxglove.Urdf", sourceType: "topic") that draws the robot. Needed because the 3D panel auto-loads a URDF only from the /robot_description parameter, which the read-only bridge withholds; a topics entry for the String topic draws nothing.
  • build_layout(cameras=DEFAULT_CAMERAS, *, compressed=False, follow_frame="base_link") -> dict[str, Any] (layout.py L293) — Builds the Foxglove layout for a scene's camera slots: a hero 3D panel (clouds + urdf_layer() robot) plus one Image panel per camera and tabbed panels for nav, joints, policy/world state, and diagnostics. Every referenced topic is on BUCKET1_TOPIC_WHITELIST, and no generated layout contains a write-capable panel. Both URDF-drawing 3D panels set scene.ignoreColladaUpAxis (Collada vertices read as authored, as RViz/Gazebo/Isaac do — OpenArm v2's Blender meshes bake Z-up yet declare Y_UP).
  • main(argv=None) -> int (layout.py L476) — python -m openral_foxglove_bringup.layout entry point: --cameras, --compressed, --follow-frame, -o/--output, --write-default (overwrite the shipped config/openral_layout.json).
  • occupied_voxel_centers(origin, resolution, size, occupancy, orientation_xyzw=(0,0,0,1)) -> list[tuple[float,float,float]] (bucket2_markers.py L68) — Pure: centre coordinates of occupied voxels, placed by the grid's own rotation — without it every voxel is drawn somewhere the obstacle is not. Raises on a mismatched occupancy length or a non-unit orientation quaternion.
  • ATTACHMENT_MARKERS_TOPIC: str (bucket2_markers.py L56) — "/openral/viz/attachments"; where the attachment / declaration-region MarkerArray is published.
  • outside_boxes(centers, boxes, pad_m) -> NDArray[np.bool_] (bucket2_markers.py L129) — Mask of voxel centres outside every (base_from_box, half_extents) box grown by pad_m; the viewer's static-voxel filter.
  • static_boxes(path) -> list[tuple[NDArray, NDArray]] (bucket2_markers.py L151) — The declared box fixtures of a StaticGeometry YAML as (base_from_box 4x4, half_extents).
  • COLLISION_MARKERS_TOPIC: str (bucket2_markers.py L176) — "/openral/viz/collisions"; where each kernel stop is drawn (latched MarkerArray).
  • COLLISION_RGBA (bucket2_markers.py L178) — solid red for a kernel stop.
  • voxel_center(index, origin, resolution, size, orientation_xyzw) (bucket2_markers.py L181) — The centre of grid cell index, the cell a kernel stop names as voxel_<index>.
  • collision_markers(stops) -> MarkerArray (bucket2_markers.py L199) — Every kernel stop so far as a red sphere + STOP <link> label, from (frame, xyz, label) triples.
  • STATIC_GEOMETRY_MARKERS_TOPIC: str (bucket2_markers.py L58) — "/openral/viz/static_geometry"; where the declared static geometry MarkerArray is published (latched, once).
  • SEARCH_BOX_RGBA: tuple[float, float, float, float] (bucket2_markers.py L275) — Faint white; the grasp declaration's search_box.
  • WORKSPACE_RGBA: tuple[float, float, float, float] (bucket2_markers.py L277) — Amber; the map-confirmed manipulation workspace (workspace_region, #349). A declared-only workspace_box is drawn in INACTIVE_REGION_RGBA.
  • MEASURED_RGBA: tuple[float, float, float, float] (bucket2_markers.py L279) — White; a payload box's measured inner box (its stage-2 refinement).
  • STATIC_RGBA: tuple[float, float, float, float] (bucket2_markers.py L281) — Translucent red; declared static geometry.
  • static_geometry_markers(path, frame) -> visualization_msgs/MarkerArray (bucket2_markers.py L507) — A StaticGeometry YAML (DeployRuntime.static_geometry_file) → DELETEALL + one CUBE per box at its pose and one TRIANGLE_LIST per mesh .npz, all in frame (the manifest base_frame the file is declared in). Raises ROSConfigError on an unreadable/invalid file or a missing/malformed mesh.
  • parse_attach_link_tf_frames(entries) -> dict[str, str] (bucket2_markers.py L224) — Pure: "link=frame" strings → manifest-link → TF-frame map, the same strings and rules as the octomap bridge's attach_link_tf_frames (payload_clearing.hpp::parse_attach_link_tf_frames, deliberate C++/Python mirror): empty entries skipped; a malformed entry or a link mapped to two frames raises ValueError.
  • EVIDENCE_RGBA: dict[str, tuple[float, float, float, float]] (bucket2_markers.py L263) — Payload marker colour per evidence_kind: grasp_target_region orange, vision_segmentation cyan, gripper_closure yellow.
  • OTHER_EVIDENCE_RGBA: tuple[float, float, float, float] (bucket2_markers.py L269) — Magenta payload colour for every evidence kind not in EVIDENCE_RGBA (sim ground truth, operator, perception track).
  • GRASP_REGION_RGBA: tuple[float, float, float, float] (bucket2_markers.py L271) — Green; an active grasp declaration's region.
  • PLACE_REGION_RGBA: tuple[float, float, float, float] (bucket2_markers.py L272) — Blue; an active place declaration's region.
  • INACTIVE_REGION_RGBA: tuple[float, float, float, float] (bucket2_markers.py L273) — Grey; a retracted (active=false) declaration's region.
  • attachment_markers(state, attach_link_tf_frames=None) -> visualization_msgs/MarkerArray (bucket2_markers.py L293) — Pure (msg types only): openral_msgs/AttachmentState → DELETEALL + re-add markers. Each attached object's primitives (box CUBE, SPHERE, capsule CYLINDER + end caps) at pose_in_link ∘ pose_in_object on the attach link's TF frame (renamed via the map), frame-locked, translucent, coloured by evidence kind, plus a text label (short id · evidence · support · released for a frozen release record); the grasp / place declaration regions (only when declaration and region are valid) as a translucent box + LINE_LIST wireframe + target label, active vs retracted coloured; a box primitive's measured inner box (first three tight_dop_lo/tight_dop_hi axes, ns attached_measured); the grasp declaration's search_box and manipulation workspace (workspace_region amber once confirmed, else workspace_box grey). Raises ValueError on a bad primitive, ROSConfigError on a degenerate quaternion.
  • class Bucket2MarkersNode (bucket2_markers.py L572) — rclpy.node.Node subscribing /openral/world_voxels and /openral/attachment_state (TRANSIENT_LOCAL); re-publishes /openral/world_voxels_cloud (PointCloud2) and /openral/viz/attachments (MarkerArray, TRANSIENT_LOCAL) via the pure functions. Parameter attach_link_tf_frames (string list, deploy_e2e passes the octomap bridge's); static_geometry_file + base_frame publish static_geometry_markers once on /openral/viz/static_geometry (TRANSIENT_LOCAL; deploy_e2e passes the scene's file); hide_static_voxels (default true) leaves the voxels inside a declared box fixture out of /openral/world_voxels_cloud (viewer only). Subscribes /openral/failure/safety and draws each kernel collision stop on /openral/viz/collisions (red sphere at the voxel centre via the grid matching the stop's disclosed origin, or at the link's frame for a link-vs-link stop). Read-only viz; defers rclpy/openral_msgs imports.
  • spin() -> None (bucket2_markers.py L789) — Spins the node until shutdown.
  • destroy() -> None (bucket2_markers.py L795) — Releases the subscriptions and publishers. Safe to call multiple times.
  • main() -> None (bucket2_markers.py L800) — Console entry point (installed as lib/openral_foxglove_bringup/bucket2_markers); rclpy.init → spin → shutdown.
  • generate_launch_description() -> LaunchDescription (bucket2.launch.py L27) — Bucket-2 converter node launch; declares use_sim_time and spawns the bucket2_markers node.
  • generate_launch_description() -> LaunchDescription (foxglove.launch.py L42) — Read-only foxglove_bridge bring-up for the Bucket-1 topics; topic_whitelist is an explicit allowlist, so anything unmatched — including safety/e-stop/action/command topics — stays invisible.
  • generate_launch_description() -> LaunchDescription (record.launch.py L35) — Opt-in MCAP recorder for the Bucket-1 allowlist (ros2 bag record -e, one regex per allowlisted topic).

packages/openral_reasoner_ros/openral_reasoner_ros/critic_producer_node.py

Tier-C critic producer, the default publisher for /openral/failure/critic. Subscribes the generic /openral/critic/score topic that any reward model publishes, routes each sample through a CriticWatchdogGroup, and on a stall emits a FailureTrigger that reasoner_node maps to a forced Tier-C tick. Advisory only.

  • class CriticProducerNode (L58) — rclpy.node.Node. Builds a CriticWatchdogGroup and a FailureBusPublisher, subscribes CriticScore, and routes each sample; on a stall publishes one KIND_CRITIC/SEVERITY_FAIL FailureTrigger per stall.
  • destroy_node() -> None (L118) — Tears down the FailureBusPublisher before base teardown.
  • main(args=None) -> None (L124) — Console entry point (installed as lib/openral_reasoner_ros/critic_producer_node.py); rclpy.init → spin → shutdown.