June 23, 2026
Minimum CMake version to support Humble at this time

REP 2000 specifies MacOS support for CMake 3.14.4 in Humble:

This is the lowest number for the whole Humble release.

However, MacOS has this note:

" ** " means that the dependency may see multiple version changes, because the dependency uses a package manager that continually updates the dependency without a stable API.

I don’t work with MacOS myself, so I don’t know how it behaves. Is it still needed nowadays to support 3.14.4 to get full support for Humble on MacOS, or can I just ignore this and upgrade to 3.16.3 from Focal (assuming that MacOS currently uses a much newer version)?

And on the other hand: Homebrew Formulae: cmake shows CMake version 4.3.4 . Does it mean that even people building Humble on MacOS will use this version? So do I need to make sure the package also builds with this version?

1 post - 1 participant

Read full topic

by peci1 on June 23, 2026 09:51 AM

Dual-Arm Nero Reinforcement Learning in Isaac Lab: Reach Task Training Example

Robotic manipulation remains one of the most important research directions in embodied AI. While traditional kinematics-based controllers provide stable motion execution, they often struggle in unstructured environments where adaptability is required.

Recent advances in Reinforcement Learning (RL) have enabled robotic arms to learn task-oriented behaviors directly from interaction, making it possible to achieve robust control policies without manually designing every motion strategy.

In this project, we extend the original SO-ARM101 Isaac Lab implementation by integrating the AgileX Dual-Arm Nero Manipulator, allowing developers to quickly train and validate dual-arm RL policies using NVIDIA Isaac Lab.

Reposity

Open-source implementation:

GitHub - smalleha/isaac_so_arm101: Isaac Lab external project for SO-ARM100/101 arm robot. · GitHub

Project Structure


├── robots

│   ├── dual_nero

│   │   ├── dual_nero.py

│   │   ├── __init__.py

│   │   ├── meshes

│   │   └── urdf

│   │       └── dual_nero.urdf

├── scripts

│   ├── rl_games

│   │   ├── play.py

│   │   └── train.py

│   └── zero_agent.py

├── tasks

│   ├── __init__.py

│   └── reach

│       ├── agents

│       ├── dual_nero_joint_pos_env_cfg.py

│       ├── dual_nero_reach_env_cfg.py

│       ├── __init__.py

│       └── mdp

└── ui_extension_example.py

Key additions include:

Column 1 Column 2
Component Description
rl_games RL-Games training framework
dual_nero_reach_env_cfg.py Reach task environment definition
dual_nero_joint_pos_env_cfg.py Joint position control configuration
dual_nero Dual-arm robot model

3. Importing the Robot into Isaac Lab

3.1 Preparing the URDF

Before importing the robot into Isaac Lab, mesh references inside the URDF should be converted to relative paths.

For example:


  <link name="base_link">

    <inertial>

      <origin rpy="0 0 0" xyz="-0.0592395620981769 -0.068642440505388 0.0562764736144042"/>

      <mass value="4.46524857458863"/>

      <inertia ixx="0.0608289280191989" ixy="-2.54649959130438E-06" ixz="1.11851948046933E-07" iyy="0.0218722514454004" iyz="-0.000689477252402357" izz="0.0680524540174318"/>

    </inertial>

    <visual>

      <origin rpy="0 0 0" xyz="0 0 0"/>

      <geometry>

        <mesh filename="../meshes/base_link.dae"/>

      </geometry>

      <material name="">

        <color rgba="0.776470588235294 0.756862745098039 0.737254901960784 1"/>

      </material>

    </visual>

    <collision>

      <origin rpy="0 0 0" xyz="0 0 0"/>

      <geometry>

        <mesh filename="../meshes/base_link.dae"/>

      </geometry>

    </collision>

  </link>

This ensures that the asset loader can correctly locate mesh resources during simulation.

3.2 Creating the Robot Configuration

Next, create a robot configuration file:

isaac_so_arm101/robots/dual_nero/dual_nero.py

This file defines:

  • Robot articulation properties

  • Joint stiffness and damping

  • Actuator configuration

  • Initial joint states

  • Gripper settings

The resulting DUAL_NERO_CFG object becomes the robot asset used by Isaac Lab during training.

from pathlib import Path

import isaaclab.sim as sim_utils
from isaaclab.actuators import ImplicitActuatorCfg
from isaaclab.assets.articulation import ArticulationCfg

TEMPLATE_ASSETS_DATA_DIR = Path(__file__).resolve().parent

DUAL_NERO_CFG = ArticulationCfg(
    spawn=sim_utils.UrdfFileCfg(
        fix_base=True,
        merge_fixed_joints=False,
        replace_cylinders_with_capsules=True,
        asset_path=f"{TEMPLATE_ASSETS_DATA_DIR}/urdf/dual_nero.urdf",
        activate_contact_sensors=False, # set as false while waiting for capsule implementation
        rigid_props=sim_utils.RigidBodyPropertiesCfg(
            disable_gravity=False,
            max_depenetration_velocity=5.0,
        ),
        articulation_props=sim_utils.ArticulationRootPropertiesCfg(
            enabled_self_collisions=True,
            solver_position_iteration_count=8,
            solver_velocity_iteration_count=0,
        ),
        joint_drive=sim_utils.UrdfConverterCfg.JointDriveCfg(
            gains=sim_utils.UrdfConverterCfg.JointDriveCfg.PDGainsCfg(stiffness=0, damping=0)
        ),
    ),
    init_state=ArticulationCfg.InitialStateCfg(
        rot=(1.0, 0.0, 0.0, 0.0),
        joint_pos={
                "left_joint.*": 0.0,
                "right_joint.*": 0.0,
                "left_gripper_joint.*": 0.0,  
                "right_gripper_joint.*": 0.0,  
        },
        # Set initial joint velocities to zero
        joint_vel={".*": 0.0},
    ),
    actuators={
            "arm": ImplicitActuatorCfg(
                joint_names_expr=["left_joint.*", "right_joint.*"],
                effort_limit=25.0, 
                velocity_limit=1.5,
               
                stiffness={
                    "left_joint1": 200.0, 
                    "left_joint2": 170.0,
                    "left_joint3": 120.0,
                    "left_joint4": 80.0,
                    "left_joint5": 50.0,
                    "left_joint6": 20.0,
                    "left_joint7": 10.0,
                    "right_joint1": 200.0, 
                    "right_joint2": 170.0,
                    "right_joint3": 120.0,
                    "right_joint4": 80.0,
                    "right_joint5": 50.0,
                    "right_joint6": 20.0,
                    "right_joint7": 10.0
                },
               
                damping={
                    "left_joint1": 100.0,
                    "left_joint2": 60.0,
                    "left_joint3": 70.0,
                    "left_joint4": 24.0,
                    "left_joint5": 20.0,
                    "left_joint6": 10.0,
                    "left_joint7": 5,
                    "right_joint1": 100.0,
                    "right_joint2": 60.0,
                    "right_joint3": 70.0,
                    "right_joint4": 24.0,
                    "right_joint5": 20.0,
                    "right_joint6": 10.0,
                    "right_joint7": 5,
                },
            ),
        "gripper": ImplicitActuatorCfg(
            joint_names_expr=["left_gripper_joint.*","right_gripper_joint.*"],
            effort_limit_sim=22,  # Increased from 1.9 to 2.5 for stronger grip
            velocity_limit_sim=1.5,
            stiffness=800.0,  # Increased from 25.0 to 60.0 for more reliable closing
            damping=20.0,  # Increased from 10.0 to 20.0 for stability
        ),

        },
    soft_joint_pos_limit_factor=0.9,
)

Create an init.py file inside the dual_nero directory so that Python recognizes it as a package.

4. Building the Reach Environment

Two environment configuration files are required:


tasks/reach/

├── dual_nero_joint_pos_env_cfg.py

└── dual_nero_reach_env_cfg.py

4.1 Joint Position Environment Configuration

dual_nero_joint_pos_env_cfg.py specifies:

  • Controlled joints

  • End-effector links

  • Action spaces

  • Command targets

import math

import isaaclab_tasks.manager_based.manipulation.reach.mdp as mdp
from isaaclab.utils import configclass
from isaac_so_arm101.robots import DUAL_NERO_CFG # noqa: F401   
from isaac_so_arm101.tasks.reach.dual_nero_reach_env_cfg import Dual_NeroReachEnvCfg
from isaaclab.assets.articulation import ArticulationCfg

@configclass
class Dual_Nero_ReachEnvCfg(Dual_NeroReachEnvCfg):
    def __post_init__(self):
        # post init of parent
        super().__post_init__()

        # switch robot to OpenArm
        self.scene.robot = DUAL_NERO_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot",)

        # override rewards
        self.rewards.left_end_effector_position_tracking.params["asset_cfg"].body_names = ["left_gripper_base"]
        self.rewards.left_end_effector_position_tracking_fine_grained.params["asset_cfg"].body_names = [
            "left_gripper_base"
        ]
        self.rewards.left_end_effector_orientation_tracking.params["asset_cfg"].body_names = ["left_gripper_base"]

        self.rewards.right_end_effector_position_tracking.params["asset_cfg"].body_names = ["right_gripper_base"]
        self.rewards.right_end_effector_position_tracking_fine_grained.params["asset_cfg"].body_names = [
            "right_gripper_base"
        ]
        self.rewards.right_end_effector_orientation_tracking.params["asset_cfg"].body_names = ["right_gripper_base"]

        # override actions
        self.actions.left_arm_action = mdp.JointPositionActionCfg(
            asset_name="robot",
            joint_names=[
                "left_joint.*",
            ],
            scale=0.5,
            use_default_offset=True,
        )

        self.actions.right_arm_action = mdp.JointPositionActionCfg(
            asset_name="robot",
            joint_names=[
                "right_joint.*",
            ],
            scale=0.5,
            use_default_offset=True,
        )
        # override command generator body
        # end-effector is along z-direction
        self.commands.left_ee_pose.body_name = "left_gripper_base"
        self.commands.right_ee_pose.body_name = "right_gripper_base"

@configclass    
class Dual_Nero_ReachEnvCfg_PLAY(Dual_Nero_ReachEnvCfg):
    def __post_init__(self):
        # post init of parent
        super().__post_init__()
        # make a smaller scene for play
        self.scene.num_envs = 50
        self.scene.env_spacing = 2.5
        # disable randomization for play
        self.observations.policy.enable_corruption = False

4.2 Reach Task Definition

dual_nero_reach_env_cfg.py contains the full RL environment definition.

This includes:

1.Scene Configuration

  • Ground plane

  • Lighting

  • Robot asset

2.Command Generation

3.Observation Space

4.Reward Design

5.Curriculum Learning

6.Episode Settings


import math
from dataclasses import MISSING

import isaaclab.sim as sim_utils
from isaaclab.assets import ArticulationCfg, AssetBaseCfg
from isaaclab.envs import ManagerBasedRLEnvCfg
from isaaclab.managers import ActionTermCfg as ActionTerm
from isaaclab.managers import CurriculumTermCfg as CurrTerm
from isaaclab.managers import EventTermCfg as EventTerm
from isaaclab.managers import ObservationGroupCfg as ObsGroup
from isaaclab.managers import ObservationTermCfg as ObsTerm
from isaaclab.managers import RewardTermCfg as RewTerm
from isaaclab.managers import SceneEntityCfg
from isaaclab.managers import TerminationTermCfg as DoneTerm
from isaaclab.scene import InteractiveSceneCfg
from isaaclab.utils import configclass
from isaaclab.utils.noise import AdditiveUniformNoiseCfg as Unoise

import isaaclab_tasks.manager_based.manipulation.reach.mdp as mdp
##
# Scene definition
##
@configclass
class Dual_NeroReachSceneCfg(InteractiveSceneCfg):
    """Configuration for the scene with a robotic arm."""

    # world
    ground = AssetBaseCfg(
        prim_path="/World/ground",
        spawn=sim_utils.GroundPlaneCfg(),
        init_state=AssetBaseCfg.InitialStateCfg(pos=(0.0, 0.0, 0)),
    )

    # robots
    robot: ArticulationCfg = MISSING

    # lights
    light = AssetBaseCfg(
        prim_path="/World/light",
        spawn=sim_utils.DomeLightCfg(color=(0.75, 0.75, 0.75), intensity=2500.0),
    )
##
# MDP settings
##
@configclass
class CommandsCfg:
    """Command terms for the MDP."""

    left_ee_pose = mdp.UniformPoseCommandCfg(
        asset_name="robot",
        body_name=MISSING,
        resampling_time_range=(4.0, 4.0),
        debug_vis=True,
        ranges=mdp.UniformPoseCommandCfg.Ranges(
            pos_x=(0.5, 0.5),
            pos_y=(0.15, 0.25),
            pos_z=(0.3, 0.5),
            roll=(-math.pi / 6, math.pi / 6),
            pitch=(3 * math.pi / 2, 3 * math.pi / 2),
            yaw=(8 * math.pi / 9, 10 * math.pi / 9),
        ),
    )

    right_ee_pose = mdp.UniformPoseCommandCfg(
        asset_name="robot",
        body_name=MISSING,
        resampling_time_range=(4.0, 4.0),
        debug_vis=True,
        ranges=mdp.UniformPoseCommandCfg.Ranges(
            pos_x=(0.5, 0.5),
            pos_y=(-0.25, -0.15),
            pos_z=(0.3, 0.5),
            roll=(-math.pi / 6, math.pi / 6),
            pitch=(3 * math.pi / 2, 3 * math.pi / 2),
            yaw=(8 * math.pi / 9, 10 * math.pi / 9),
        ),
    )

@configclass
class ActionsCfg:
    """Action specifications for the MDP."""
    left_arm_action: ActionTerm = MISSING
    right_arm_action: ActionTerm = MISSING
@configclass
class ObservationsCfg:
    """Observation specifications for the MDP."""

    @configclass
    class PolicyCfg(ObsGroup):
        """Observations for policy group."""

        # observation terms (order preserved)
        left_joint_pos = ObsTerm(
            func=mdp.joint_pos_rel,
            params={
                "asset_cfg": SceneEntityCfg(
                    "robot",
                    joint_names=[
                        "left_joint.*",
                    ],
                )
            },
            noise=Unoise(n_min=-0.01, n_max=0.01),
        )

        right_joint_pos = ObsTerm(
            func=mdp.joint_pos_rel,
            params={
                "asset_cfg": SceneEntityCfg(
                    "robot",
                    joint_names=[
                        "right_joint.*",
                    ],
                )
            },
            noise=Unoise(n_min=-0.01, n_max=0.01),
        )

        left_joint_vel = ObsTerm(
            func=mdp.joint_vel_rel,
            params={
                "asset_cfg": SceneEntityCfg(
                    "robot",
                    joint_names=[
                        "left_joint.*",
                    ],
                )
            },
            noise=Unoise(n_min=-0.01, n_max=0.01),
        )
        right_joint_vel = ObsTerm(
            func=mdp.joint_vel_rel,
            params={
                "asset_cfg": SceneEntityCfg(
                    "robot",
                    joint_names=[
                        "right_joint.*",
                    ],
                )
            },
            noise=Unoise(n_min=-0.01, n_max=0.01),
        )
        left_pose_command = ObsTerm(func=mdp.generated_commands, params={"command_name": "left_ee_pose"})
        right_pose_command = ObsTerm(func=mdp.generated_commands, params={"command_name": "right_ee_pose"})
        left_actions = ObsTerm(func=mdp.last_action, params={"action_name": "left_arm_action"})
        right_actions = ObsTerm(func=mdp.last_action, params={"action_name": "right_arm_action"})

        def __post_init__(self):
            self.enable_corruption = True
            self.concatenate_terms = True

    # observation groups
    policy: PolicyCfg = PolicyCfg()

@configclass
class EventCfg:
    """Configuration for events."""

    reset_robot_joints = EventTerm(
        func=mdp.reset_joints_by_scale,
        mode="reset",
        params={
            "position_range": (0.5, 1.5),
            "velocity_range": (0.0, 0.0),
        },
    )

@configclass
class RewardsCfg:
    """Reward terms for the MDP."""
    left_end_effector_position_tracking = RewTerm(
        func=mdp.position_command_error,
        weight=-0.2,
        params={
            "asset_cfg": SceneEntityCfg("robot", body_names=MISSING),
            "command_name": "left_ee_pose",
        },
    )
    right_end_effector_position_tracking = RewTerm(
        func=mdp.position_command_error,
        weight=-0.25,
        params={
            "asset_cfg": SceneEntityCfg("robot", body_names=MISSING),
            "command_name": "right_ee_pose",
        },
    )
    left_end_effector_position_tracking_fine_grained = RewTerm(
        func=mdp.position_command_error_tanh,
        weight=0.1,
        params={
            "asset_cfg": SceneEntityCfg("robot", body_names=MISSING),
            "std": 0.1,
            "command_name": "left_ee_pose",
        },
    )
    right_end_effector_position_tracking_fine_grained = RewTerm(
        func=mdp.position_command_error_tanh,
        weight=0.2,
        params={
            "asset_cfg": SceneEntityCfg("robot", body_names=MISSING),
            "std": 0.1,
            "command_name": "right_ee_pose",
        },
    )
    left_end_effector_orientation_tracking = RewTerm(
        func=mdp.orientation_command_error,
        weight=-0.1,
        params={
            "asset_cfg": SceneEntityCfg("robot", body_names=MISSING),
            "command_name": "left_ee_pose",
        },
    )

    right_end_effector_orientation_tracking = RewTerm(
        func=mdp.orientation_command_error,
        weight=-0.1,
        params={
            "asset_cfg": SceneEntityCfg("robot", body_names=MISSING),
            "command_name": "right_ee_pose",
        },
    )
    # action penalty
    action_rate = RewTerm(func=mdp.action_rate_l2, weight=-0.0001)
    left_joint_vel = RewTerm(
        func=mdp.joint_vel_l2,
        weight=-0.0001,
        params={
            "asset_cfg": SceneEntityCfg(
                "robot",
                joint_names=[
                    "left_joint.*",
                ],
            )
        },
    )
    right_joint_vel = RewTerm(
        func=mdp.joint_vel_l2,
        weight=-0.0001,
        params={
            "asset_cfg": SceneEntityCfg(
                "robot",
                joint_names=[
                    "right_joint.*",
                ],
            )
        },
    )
@configclass
class TerminationsCfg:
    """Termination terms for the MDP."""
    time_out = DoneTerm(func=mdp.time_out, time_out=True)
@configclass
class CurriculumCfg:
    """Curriculum terms for the MDP."""
    action_rate = CurrTerm(
        func=mdp.modify_reward_weight,
        params={"term_name": "action_rate", "weight": -0.005, "num_steps": 4500},
    )

    left_joint_vel = CurrTerm(
        func=mdp.modify_reward_weight,
        params={"term_name": "left_joint_vel", "weight": -0.001, "num_steps": 4500},
    )

    right_joint_vel = CurrTerm(
        func=mdp.modify_reward_weight,
        params={"term_name": "right_joint_vel", "weight": -0.001, "num_steps": 4500},
    )
##
# Environment configuration
##
@configclass
class Dual_NeroReachEnvCfg(ManagerBasedRLEnvCfg):
    """Configuration for the reach end-effector pose tracking environment."""
    # Scene settings
    scene: Dual_NeroReachSceneCfg = Dual_NeroReachSceneCfg(num_envs=4096, env_spacing=2.5)
    # Basic settings
    observations: ObservationsCfg = ObservationsCfg()
    actions: ActionsCfg = ActionsCfg()
    commands: CommandsCfg = CommandsCfg()
    # MDP settings
    rewards: RewardsCfg = RewardsCfg()
    terminations: TerminationsCfg = TerminationsCfg()
    events: EventCfg = EventCfg()
    curriculum: CurriculumCfg = CurriculumCfg()

    def __post_init__(self):
        """Post initialization."""
        # general settings
        self.decimation = 2
        self.sim.render_interval = self.decimation
        self.episode_length_s = 24.0
        self.viewer.eye = (3.5, 3.5, 3.5)
        # simulation settings
        self.sim.dt = 1.0 / 60.0



\`\`\`



**### 4.3 Registering the Environment**



Register the task inside:\`src/isaac_so_arm101/tasks/reach/\__init_\_.py\`



\`\`\`PYTHON

gym.register(

    id="Isaac-Dual-Nero-Reach-v0",

    entry_point="isaaclab.envs:ManagerBasedRLEnv",

    kwargs={

        "env_cfg_entry_point":f"{\__name_\_}.dual_nero_joint_pos_env_cfg:Dual_Nero_ReachEnvCfg",

        "rsl_rl_cfg_entry_point": f"{agents.\__name_\_}.rsl_rl_ppo_cfg:ReachPPORunnerCfg",

        "rl_games_cfg_entry_point": f"{agents.\__name_\_}:rl_games_ppo_cfg.yaml",



    },

    disable_env_checker=True,

)

5. Training the Reach Policy

Step 1.Activate the Isaac Lab Environment


conda activate env_isaaclab

Step 2.Navigate to the project directory:


cd isaac_so_arm101

Step 3.Train the whole project

Option 1:RSL-RL

  • Train:

uv run train \

    --task Isaac-Dual-Nero-Reach-v0 \

    --headless

  • Evaluate:

uv run play \

    --task Isaac-Dual-Nero-Reach-v0

  • Training result:

dual_nero_rsl_rl.gif

dual_nero_rsl_rl

Option 2: RL-Games

  • Train:

python3 scripts/rl_games/train.py \
    --task Isaac-Dual-Nero-Reach-v0 \
    --headless

  • Evaluate:

python3 scripts/rl_games/play.py \
    --task Isaac-Dual-Nero-Reach-v0
  • Training result:

dual_nero_rl_games.gif

dual_nero_rl_games

6. Results and Observations

Both frameworks successfully learn the dual-arm reaching task.

However, in our experiments:

  • RL-Games converges faster

  • Motion trajectories appear smoother

  • Final reaching accuracy is generally higher

For relatively complex robot morphologies such as dual-arm manipulators, RL-Games currently provides more stable performance and is recommended as the default training backend.

FAQ

Q1: Why is my Dual-Nero robot collapsing or shaking violently after loading the URDF?

This is usually caused by incorrect actuator parameters or unrealistic inertial properties.

Common causes include:

  1. Joint stiffness set too high

  2. Damping values too low

  3. Incorrect mass distribution in the URDF

  4. Self-collision configuration issues

  5. Unstable simulation timestep

Before starting RL training, verify that the robot can remain stable under gravity using only PD control.

Quick check: If the robot cannot stand still without RL, the issue is likely in the robot model rather than the training algorithm.

Q2: Why does the reward improve, but the robot never reaches the target accurately?

A rising reward does not always indicate successful task completion.

Typical reasons include:

  • Reward weights are unbalanced

  • Orientation rewards dominate position rewards

  • Action penalties are too strong

  • Command sampling range is too large

  • End-effector link is incorrectly configured

In most reach tasks, incorrect reward shaping is the primary reason for poor final accuracy.

Q3: How do I verify that the end-effector link is configured correctly?

One of the most common mistakes in Isaac Lab reach tasks is assigning the wrong end-effector body.

For Dual-Nero, the target link should be:


left_gripper_base

right_gripper_base

Symptoms of an incorrect configuration include:

  • Reward remains low

  • Robot moves randomly

  • Training appears to converge but fails visually

  • End-effector does not move toward the target marker

Always verify the body name in Isaac Sim before launching large-scale training.

Q4: Why does RL-Games perform better than RSL-RL for this task?

Both frameworks are PPO-based, but their implementations differ.

For large-scale manipulation environments:

RL-Games generally scales better with thousands of parallel environments

PPO updates are often more stable

Training throughput is higher on modern GPUs

For Dual-Nero reach experiments, RL-Games typically achieves smoother trajectories and faster convergence.

However, results may vary depending on reward design and task complexity.

Q5: My policy works in simulation but fails on the real robot. Why?

This is the most common Sim-to-Real issue.

Possible causes include:

  • Joint friction mismatch

  • Encoder noise

  • Latency differences

  • Payload variations

  • Inaccurate motor models

  • Unmodeled cable effects

To improve transfer performance:

  • Apply domain randomization

  • Add observation noise

  • Randomize dynamics parameters

  • Validate trajectories at low speed first

Successful simulation training is only the first step toward real-world deployment.

:speech_balloon: Have Question?

If you encounter any issues with environment installation, parameter configuration, or RL training, feel free to leave your questions for further discussion.

1 post - 1 participant

Read full topic

by Agilex_Robotics on June 23, 2026 03:50 AM

June 22, 2026
Open-source ROS2 LiDAR robot vacuum cleaner

Hello, would you be interested in building a ROS2 vacuum cleaner robot?

If yes

  1. Build/develop vs convert existing?
  2. 3D print vs off-the-shelf?
  3. Convert existing - root or swap board?

I’d really appreciate your input.

As an example of what I’m doing

1 post - 1 participant

Read full topic

by iliao on June 22, 2026 06:12 PM

June 21, 2026
AIC Phase 1: How to run custom aic_model service in Flowstate with AIC_ROUTER_ADDR?

Hi Intrinsic / AIC team,

I’m working on AIC Phase 1 in Flowstate and trying to run my participant model as a custom Service instance named exactly aic_model.

The baseline AIC Phase 1 Submission process expects:

  • lifecycle service: /aic_model/change_state
  • action server: /insert_cable

I have a local Docker image:

my-solution:phase1_basic_control_ladder

The image works in local Docker Compose when launched with the AIC router environment variables. Its entrypoint requires:

AIC_ROUTER_ADDR

and may require AIC_MODEL_PASSWD if ACL is enabled.

From inspecting the SDK/Flowstate tooling, it looks like the possible path is:

inctl asset install <bundle.tar> --org ... --cluster ...
inctl service add <service_asset_id_version> --cluster ... --name aic_model

I can package the Docker image as a Service asset bundle, but I don’t want to guess the router environment contract.

Questions:

  1. For a custom Flowstate Service instance named aic_model, does Flowstate automatically inject AIC_ROUTER_ADDR and AIC_MODEL_PASSWD?
  2. If not, what should AIC_ROUTER_ADDR be set to in simulation inside the Flowstate VM?
  3. What is the approved way to provide AIC_MODEL_PASSWD without exposing secrets?
  4. Is inctl asset install + inctl service add --name aic_model the intended local Flowstate testing workflow for AIC Phase 1, or is there another approved way to make the participant model available to the solution?

Thanks!

1 post - 1 participant

Read full topic

by Young_Jeff on June 21, 2026 06:21 AM

June 20, 2026
Introducing CycloneDDS Insight - Graphical DDS Inspection and Debugging for ROS 2

Introducing CycloneDDS Insight - Graphical DDS Inspection and Debugging for ROS 2.

CycloneDDS Insight is a graphical DDS inspection and debugging tool for ROS 2 and Eclipse Cyclone DDS.

The tool provides visibility into DDS participants, topics, publishers and subscribers, helping users understand system topology, diagnose discovery issues and analyze communication behavior in distributed ROS 2 systems.

Features include:
* Live inspection of DDS entities
* Discovery and communication debugging
* Visualization of communication architectures
* DDS traffic and statistics monitoring
* Support for complex multi-host ROS 2 deployments

CycloneDDS Insight is completely free and open source, making advanced DDS inspection and debugging capabilities accessible to everyone in the ROS 2 community.

Repository:
https://github.com/eclipse-cyclonedds/cyclonedds-insight

Feedback and contributions are welcome.

1 post - 1 participant

Read full topic

by trittsv on June 20, 2026 10:10 PM

Scale Models of ROS robots

A few months ago I had an odd thought (as an occasional modelmaker), has anyone ever made a model of a ROS robot? An AGV, a PR2 to sit on your desk? A Moose? It should be fairly straightforward even, because practically all robot description packages ship a mesh of the robot for rviz which can be fixed up a little and 3D printed to get an exact replica.

I’ve searched quite a bit but couldn’t find anything like that online. So I guess it fell to me to make the first entry.

I got the idea when looking at the ST3045M, the microservo variant of the really practical ST3215 that everyone’s using in SO-ARMs. Something so tiny with full positional and velocity control, low speed torque, odom feedback, and one wire control is like the holy grail for modelmaking.

My first plan was Segway’s Nova Carter, since it’s a fairly aesthetic design, but some miscalculations later I realized it’s way too narrow for it to work with these servos. Clearpath’s Husky or Jackal would work, but then I found Husarion’s Lynx which both looks cooler and the wheel shafts line up almost perfectly when scaled down.

So a few months later, here’s the end result: A 1/5 scale model with most of the capabilities of the real thing, except maybe runtime and a slightly lower scale top speed:

The shell is 3D printed PLA, spray painted, plus some minor weathering with chrome paint, the tires are just off the shelf 1/24 scale crawler wheels with printed hubs. I’ve used double ended LED filaments to make the RGB strips, even the WS2812C is too large at this scale.

I had to modify the servos a bit, sawing off the mounting plates on one side so the shafts would be close enough to the end of the chassis without them poking out. They occupy the entire bottom third:

I was planning to use a Pi Zero 2, but the performance meant it would never be able to run anything useful. The only realistic other option was a CM4 + a drone carrier board to leave me enough space for the other stuff. Expensive but totally worth it imo, since it also has very compact pixhawk connectors and an external antenna plug. I had to take some creative liberties in the rear, extending it out a bit and flattening it so the parts would fit.

(no the tiny estop doesn’t work, sorry :joy:)

As for how I made it run nav2 without a lidar is… well that it does actually have a built in lidar. There’s a Vl53L7CX in the front that behaves a lot like a really garbage stereo camera with only 64 pixels, from which I can extract a horizontal line or two, make some hilarious assumptions and boom a laserscan. Needless to say slam_toolbox recoils in horror unless I disable scan matching, but the wheel odom + BNO085 is actually totally enough to map that way for short periods.

And here are the internals, I’m not really patient enough to make my own PCBs, so it’s just a bunch of dev boards with gratuitous components removed, wrapped in kapton tape and shoved in there.

My enemy throughout the build was heat. I’ve never attempted anything this compact before and with such high power components the second chassis just melted around the CM4. I thought passive cooling will be enough, but even with a heatsink the size of the entire rear end it didn’t really help. So it needs the fan, and extra vents in the wheel wells and the top to help keep the battery cool too. The wheels are ultra soft, which is nice for grip (it can climb up a 30 deg slope out of smooth metal) but they also almost tear off when turning in place on rough surfaces.

The pogo pins in the front are for charging and I have an apriltag dock planned, but I still need to find a usb webcam that looks scale and design a gantry that mounts on top…

I’ve documented most of the setup, since it’s convoluted enough that I’ll forget half of it in two months otherwise, but hopefully it comes in useful for anyone else, there’s some videos there too:

I wouldn’t really recommend anyone to try and recreate it strictly based on that since it’s hardly complete, but I really hope it comes in handy as an inspiration or learning reference. I would just love to see if anyone else has already made something but just never posted it, or if hopefully this gives someone the idea to make something similar. I think a Nova Carter is genuinely doable with geared micro steppers after seeing this thing the other day.

4 posts - 2 participants

Read full topic

by MoffKalast on June 20, 2026 01:46 PM

June 19, 2026
ZeroDDS: a pure-Rust RMW for ROS 2 (rc.3), built against 349 real ROS↔DDS pain reports

Full disclosure up front: I work on ZeroDDS, so this is our project, not a neutral review. Sharing it here because it’s specifically aimed at problems this community has been reporting for years, and we’d genuinely like the feedback.

What it is: an alternative RMW for ROS 2, with the whole DDS stack underneath written from scratch in Rust (memory-safe, Apache-2.0, no per-seat license). It speaks native RTPS 2.5 and interoperates with your existing setup: ROS 2 over `rmw_zerodds` talks to ROS 2 over `rmw_cyclonedds` bidirectionally (20/20 in our pub/sub + service tests). It’s a drop-in `RMW_IMPLEMENTATION`, not a fork of your graph.

We didn’t guess at the pain, we catalogued it. Before writing “ROS 2 support” anywhere, we collected 349 real-world ROS↔DDS pain reports from this forum, GitHub and elsewhere, and engineered against the recurring ones:

  • Silent QoS / type mismatches — the classic where two endpoints just never match and nothing is logged. Fixed the keyed/no-key entity-id case, and shipped `zerodds-ros2-shim doctor`, a discovery-independent check that flags the most common WiFi misconfig (multicast-free with no unicast peers) as a hard error instead of a silent no-match.

  • WiFi discovery loss — a single SPDP beacon dropped during 802.11 power-save leaves you at `participants=0`. We send an initial-announcement burst (10×200 ms until matched), like FastDDS’ `initial_announcements`. Verified on a real WiFi rig, not just loopback.

  • Large fleets / WAN — a `multi_robot()` profile: unicast-only discovery, no multicast storm.

  • DDS-Security / SROS2. ZeroDDS consumes SROS2 enclave files (identity/permissions CA, cert, key, governance, permissions) and fails hard on a broken config (no silent downgrade). Caveat for ROS-users: today you point it at the enclave via a ZERODDS_SECURITY_DIR env var, not the standard ROS_SECURITY_KEYSTORE / ROS_SECURITY_ENABLE variables yet. Wiring those up so ros2 security works unmodified is still open.

  • Introspection — `ros2 topic info -v` resolves endpoint GUIDs to node names.

Works with rclpy and rclcpp.

New in rc.3: zero-copy shared memory now works on Windows too (via `CreateFileMapping` + `LockFileEx`), tested on a real Windows runner.

Next (rc.4): a continuous multi-distro live `ros2` smoke as a CI gate across Humble, Iron and Jazzy, so interop is proven on every commit rather than by hand. The `rmw_zerodds` surface itself is code-complete (pub/sub, services, wait-sets, loaned messages, REP-2009 type hash + endpoint info; nothing left `UNSUPPORTED`).

Honest about maturity: it’s a release candidate, not years-hardened. On performance we’re on par with the established stacks across real workloads, not uniformly ahead, and we don’t claim to beat RT-tuned commercial configs. An OMG Vendor-ID is filed but not yet assigned. We are not safety-certified.

Try it:

If you hit a mismatch with your Cyclone/Fast-DDS nodes, or a QoS combination that doesn’t behave, that’s exactly the feedback we want. Tell us where it breaks.

(For the DDS/non-ROS folks who wander in: the same codebase also covers XTypes, DDS-Security, RPC, bridges, seven language bindings, and a full CORBA 3.3 ORB. Happy to go into any of it, but I’ll keep this post ROS-focused.)

1 post - 1 participant

Read full topic

by SandraK82 on June 19, 2026 10:12 PM

A VS Code extension for quickly running ROS2 launch files, Python nodes, and C++ nodes

ROS2 Quick Runner

This is a VSCode extension I recently wrote, and I’ve been using it lately

A VS Code extension for quickly running ROS2 launch files, Python nodes, and C++ nodes.

GitHub - Knighthood2001/vscode-ros2-quick-runner: Quickly run ROS 2 launch, Python, and C++ files in VSCode · GitHub

Features

1. Launch ROS2 Files

Right-click any .launch.py file and select “ros2 launch” to:

  • Automatically find the ROS2 workspace
  • Source the workspace’s install/setup.bash
  • Execute ros2 launch <package_name> <launch_file>

2. Run ROS2 Nodes

Right-click any .py or .cpp file and select “ros2 run” to:

  • Automatically find the ROS2 workspace and package name
  • Source the workspace’s install/setup.bash
  • Execute ros2 run <package_name> <node_name>

3. Get Workspace Name

Right-click any file and select “ros2 source” to:

  • Display the workspace name
  • Source the workspace in a new terminal

4. Build ROS2 Workspace

Right-click any folder in your ROS2 workspace and select “colcon build” to:

  • Automatically find the ROS2 workspace root
  • Open a terminal at the workspace root
  • Execute colcon build

Smart path detection:

  • Right-click xxx_ws/ → build in xxx_ws/
  • Right-click xxx_ws/src/ → build in xxx_ws/
  • Right-click any sub-folder (e.g. xxx_ws/src/pkg_a) → build in xxx_ws/

How It Works

The extension automatically:

  1. Finds the ROS2 workspace by searching for directories whose src/ subdirectory contains at least one package with package.xml
  2. Extracts the package name by parsing the package.xml file in the package directory
  3. Executes commands in a new VS Code terminal

Usage

  1. Search for ros2-quick-runner in VS Code extensions and install it
  2. Open your ROS2 project in VS Code
  3. Right-click on a file in the explorer:
    • .launch.py files → “ros2 launch”
    • .py or .cpp files → “ros2 run”
    • Any file → “ros2 source”
    • Any folder → “colcon build”

Commands

Command Description
ros2-quick-runner.ros2launch Launch a .launch.py file
ros2-quick-runner.ros2run Run a Python or C++ node
ros2-quick-runner.getWorkspaceName Get and source the workspace
ros2-quick-runner.colconBuild Build the ROS2 workspace

Requirements

  • VS Code 1.80.0 or higher
  • ROS2 installed (e.g., ROS 2 Humble)
  • A compiled ROS2 workspace with install/ directory

Release Notes

For detailed changelog, please see: vscode-ros2-quick-runner/CHANGELOG.md at main · Knighthood2001/vscode-ros2-quick-runner · GitHub

0.0.3

  • Initial release
  • Support for ROS2 launch files, Python nodes, and C++ nodes
  • Automatic workspace detection
  • Automatic package name extraction

1 post - 1 participant

Read full topic

by Knighthood2001 on June 19, 2026 12:22 AM

June 18, 2026
ROS 2 Robot V1.0.0 - A Modern GUI for ROS 2 Workspace Management (Like GitHub Desktop for ROS)

Hi everyone,

“ROS is used to build robots, but isn’t it time for ROS to have its own robot?�

I’m excited to share a tool I’ve been working on to make ROS 2 development a bit smoother: Ros2 Robot.

As developers, we spend a lot of time typing repetitive terminal commands just to manage packages, source workspaces, and inspect topics. I wanted to build a tool that handles the boilerplate so we can focus on actual robotics innovation. It also serves as a great stepping stone for newcomers who find the ROS 2 CLI overwhelming.

What is it?
Ros2 Robot is a PyQt/PySide6 based GUI that acts like “GitHub Desktop� for your ROS 2 projects.

Key Features in V1.0.0:

  • Create packages, rebuild, and source them all with one click.
  • Run and manage nodes all from one place.
  • Inspect topics in real-time.
  • Manage ROS bags effortlessly.
  • Build launch files by adding blocks.
  • Visualize your URDF files and control your joints.
  • Open your favorite ROS Plugins directly from the interface.
  • Educational: understand what each feature does in the background CLI.
  • Supports Native Linux (Tested On Ubuntu 22.04/24.04) and Windows with WSL 2!

:rocket: Installed with ��� ����, and launched with ��� �������. :rocket:

:laptop: GitHub Repository: GitHub - saheraalreqeb/Ros2_Robot: Ros2 Robot is a simple GUI designed to help you manage your ROS 2 projects a. Think of it as GitHub Desktop, but for ROS 2. It eliminates those repetitive, soul-crushing terminal commands so you can actually focus on coding and development. · GitHub
(I’ll be posting a full video tutorial later today showing it in action!)

I’m releasing this as V1.0.0 because the core features are stable, but this is just the beginning. I would love for the community to test it out. If you encounter bugs, have ideas for new features, or want to contribute code, please head over to the GitHub repo and open an issue or a PR.

Let me know what you think!

screenshots_carousel

2 posts - 1 participant

Read full topic

by saheralreqeb on June 18, 2026 03:03 PM

Seeking feedback: Is dependency blocking during ROS development a significant pain point?

Seeking Feedback from Robotics Software Engineers / ROS Developers

Hello everyone,

I hope you are doing well.

I am a BE Computer Science student with a strong interest in robotics software. I am currently researching a potential software product and, before investing significant time into building it, I would like to validate whether the problem I am trying to solve is a genuine challenge faced by robotics engineers.

From my research, I have observed that robotics engineers are often blocked because another part of the robot is not yet available or is temporarily broken. For example:

  • A navigation engineer cannot continue because localization is not ready.
  • A manipulation engineer cannot continue because MoveIt is unavailable or malfunctioning.
  • A docking engineer cannot test their feature because the navigation stack is incomplete.

To continue development, engineers often write temporary Python scripts, use ROS CLI commands, or create custom test nodes just to simulate missing robot components.

The idea I am exploring is a ROS-native platform that allows engineers to discover, call, mock, save, and reuse ROS 2 Actions and Services through a visual interface, helping them continue development without waiting for missing dependencies.

Before building anything, I would sincerely appreciate your honest opinion.

  1. Have you experienced dependency-blocking situations like these in your projects?
  2. How do you currently work around these problems?
  3. If this problem exists, how painful is it in day-to-day development?
  4. Do you think a tool like this would provide meaningful value, or am I solving the wrong problem?
  5. Is there any existing tool that already solves this problem well?
  6. Is there anything important that I might be overlooking?

I genuinely welcome honest criticism, even if your opinion is that this problem is not worth solving. Constructive feedback would help me avoid spending months building something that isn’t valuable to the robotics community.

If anyone is willing to discuss this in more detail, I would be sincerely grateful for even 10–15 minutes of your time through a direct message, phone call, or Google Meet at your convenience.

Thank you very much for taking the time to read this post. I truly appreciate any feedback, suggestions, or guidance you are willing to share.
is this good and worth posting

2 posts - 2 participants

Read full topic

by kameshwaran on June 18, 2026 03:02 PM

Has anyone tried conformal prediction for sensor gating in a nav stack?

Been thinking about this for a while.

The chi-squared gate in most localization stacks assumes Gaussian noise. When when we look outdoors… it rarely is. Like doing multipath near buildings, or under tree canopy, and even around field equipment.

I came across this conformal prediction (Angelopoulos & Bates 2022). The idea is: instead of assuming a distribution, you basically test each new measurement against your own empirical data. It states coverage guarantees hold regardless of noise shape.

Has anyone tried something like this in a nav stack? And honestly… is GPS covariance mismatch painful enough in real deployments that it’s worth a proper fix, or does tuning R get you 90% of the way there?

2 posts - 2 participants

Read full topic

by manankharwar on June 18, 2026 04:14 AM

June 16, 2026
ROS2 on STM32 serial to SBC

Hi all,

I’m building a small ROS 2 robot with an STM32 microcontroller and an SBC running a Zenoh router (rmw_zenoh). I want to keep the MCU side Zenoh-native with Zephyr RTOS, and I’m trying to figure out the best communication stack between the STM32 and the rest of the graph.

I’ve been looking at three options:

  1. micro-ROS (classic) — well documented, but I’m not sure whether I’d still need zenoh-bridge-dds on the SBC side given I’m already running rmw_zenoh there
  2. Zenoh-Pico with rmw_zenoh_pico (eSOL) — conceptually a perfect fit, but the repo seems to only target Linux/RPi so far, and I’m not sure how much work porting to STM32 + Zephyr would be
  3. Pico-ROS — appeals to me architecturally (native Zenoh, no micro-ROS overhead), but documentation is very sparse and I couldn’t find real-world STM32 examples

I’m not necessarily looking for a solution that works out of the box — I’m happy to do integration work. What I’m really after is whether anyone has actually tried any of these on STM32 + Zephyr, what pain points they hit, and whether there’s a better path I’m missing.

Any experience or pointers would be greatly appreciated!

7 posts - 5 participants

Read full topic

by Alvin0523 on June 16, 2026 04:05 PM

June 15, 2026
Bringing the ROS Community Together: Meetups in Heilbronn and Karlsruhe

Beyond the large annual conferences, some of the most valuable moments in the ROS ecosystem happen at a smaller scale, when local developers, researchers, and companies gather in one room for an afternoon of talks, demos, and conversation. Over the spring, the robotics community had two such occasions: the first-ever ROS Meetup in Heilbronn in March, followed by the second ROS Meetup in Karlsruhe in May. Both events reflected a healthy, growing grassroots scene around ROS 2, and both reaffirmed why face-to-face exchange remains so important to the open-source robotics community.

A First for Heilbronn

On 23 March 2026, the robotics community came together at the TUM Campus Heilbronn (Bildungscampus) for the first ROS Meetup ever hosted in the city. The event was organized jointly by Neobotix GmbH, TUM Campus Heilbronn, and Fraunhofer IPA, and brought together developers, researchers, and students for an afternoon of talks, lab tours, and networking over a shared lunch at the Bildungscampus.

The afternoon opened with an introduction from Neobotix GmbH before moving into a full technical program. Denis Stogl of b-robotized presented recent improvements to real-time control in ros2_control, focusing on coordinating multiple robots, an area where reliable, deterministic control is essential for industrial adoption. Robert Wilbrandt of the FZI Forschungszentrum Informatik for Information Technology introduced a reusable, MoveIt-based manipulation planning component designed to simplify the integration of motion planning across projects. Vishnuprasad Prachandabhanu and Sanjeev Kumar from Fraunhofer IPA followed with a talk on developing ROS 2 controllers for the Unitree G1 humanoid platform.

The lineup also reached into learning, general-purpose robotics, and safety. Pauline Steffel, a PhD student in the AImotion department, presented work toward a configurable and reusable reinforcement-learning training infrastructure for autonomous mobile robots in ROS 2. Tobias Weyer of TNG Technology Consulting shared a broader perspective on general-purpose robotics with ROS, and Zhen Zhang of TUM Campus Heilbronn closed the talks with research on safe, LLM-controlled robots that provide formal guarantees through reachability analysis.

The presentations were complemented by hands-on demonstrations. Attendees were given an inside look at the research underway in the TUM Cyber-Physical Systems labs, and Neobotix showed several of its professional-grade mobile robots running live. As is often the case, the networking session afterward proved just as valuable as the talks themselves, providing the kind of informal, face-to-face exchange that turns into future collaboration.

Organizing a community event from scratch is no small undertaking, and the success of this first edition owed much to the team at Neobotix together with TUM Campus Heilbronn's Cyber-Physical Systems group, including Prof. Amr Alanwar, Zhen Zhang, and Hadi Elnemr, and the support staff who made the venue and logistics work. ROS-Industrial Europe and Fraunhofer IPA were glad to collaborate on bringing the meetup to life. For a first event, it set an encouraging precedent and showed clear appetite for a recurring gathering in the region.

A Second Round in Karlsruhe

Two months later, on 21 May 2026, the community reconvened at the FZI Forschungszentrum Informatik House of Living Labs in Karlsruhe for the second ROS Meetup hosted there. With six presentations spanning research and industry, the afternoon offered a broad cross-section of how ROS is being applied today, from manipulation and motion planning to medical robotics and industrial machine tools.

Robert Wilbrandt of the FZI Forschungszentrum Informatik for Information Technology opened the technical program with an overview of how ROS sits at the core of FZI's robotics projects. He highlighted several open-source packages the center maintains, including vdb_mapping for long-term, large-scale 3D mapping and navigation, behavior-tree-based approaches for the automatic, LLM-driven generation of assembly and disassembly programs. Dr. Jennifer Bühler and Dr. Denis Stogl of the b-robotized group followed with their approach to the Intrinsic Industrial AI Challenge using HIL-SERL (Human-in-the-Loop Sample-Efficient Reinforcement Learning), a method that combines a small number of demonstrations with targeted human interventions during training so that manipulation tasks can be learned with far less task-specific data than many current approaches.

The motion-planning thread continued with Sebastian Jahr of the ZEISS Group, who introduced OInK, an optimal inverse kinematics solver built on Roboplan, an emerging library based on Pinocchio. OInK is a QP-based differential IK solver written in C++ that computes joint commands in real time while tracking multiple objectives and respecting constraints and safety barriers. Dr.-Ing. Marius Siegfarth and Javier Moviglia from the Mannheim Institute for Intelligent Systems in Medicine (Medical Faculty Mannheim of Heidelberg University) then turned the focus to medical robotics, showing how they use ROS to develop robotic prototypes for operating-room automation, integrate them with imaging modalities such as CT and MRI, and connect devices within the OR environment.

Rounding out the program, Matthias Mayr of Mayr Robotics presented a Cartesian impedance control stack for torque-controlled manipulators. Built with ros2_control, it enables the compliant interaction that many real-world manipulation tasks require and generalizes across platforms such as the Franka Research 3 and the KUKA iiwa. Finally, Matthias Marquart (ISW, University of Stuttgart) and Benjamin Kaiser (ISG – Industrielle Steuerungstechnik GmbH) bridged ROS and industrial CNC. Motivated by use cases such as robotic timber milling, they showed a hybrid architecture in which a TwinCAT CNC triggers a MoveIt planner for collision-free path planning and then executes the resulting path back on the CNC, combining industrial reliability with the flexibility of the ROS ecosystem, before looking ahead to native integration of the ISG-Kernel SDK into ROS.

The meetup wrapped up with a tour of FZI's robotics labs, including a showcase of the center's custom-developed legged robots, followed by more networking.

Why These Events Matter

Taken together, the Heilbronn and Karlsruhe meetups illustrate something the ROS-Industrial community has long believed: real-world progress depends as much on people connecting as on code being written. The talks spanned the full breadth of the field, from low-level real-time control and inverse kinematics to reinforcement learning, medical automation, and CNC integration, while the demos and lab tours grounded those ideas in working hardware.

A sincere thank you goes to everyone who made these gatherings possible: the organizers at Neobotix, TUM Campus Heilbronn, FZI Forschungszentrum Informatik, and Fraunhofer IPA, the speakers who shared their work, and the many attendees who brought their curiosity and ideas. With two successful meetups behind us, we look forward to seeing this regional community continue to grow, and to many more afternoons of talks, demos, and good conversation.

Image.png
5N3A2045-2.jpg
5N3A2072-2.jpg
5N3A2140-2.jpg
5N3A2189-2.jpg
5N3A2320-2.jpg
5N3A2294-2.jpg
5N3A2369-2.jpg
5N3A2336-2.jpg

by Vishnuprasad Prachandabhanu on June 15, 2026 06:20 PM

Looking for a repository within ROS2 to support

Hello guys, I have history with C, python and have worked with ROS2 and ROS2 controls, for Control Theory stuff. I would like to be involved in supporting a repository or a stable project. Kindly recommend a starting point. You can also check my github to see if I woul be suitable for your project at KevinKipkorir254.

3 posts - 3 participants

Read full topic

by Kevin_Kipkorir on June 15, 2026 01:52 PM

ROS2 Studio Update

ROS2-STUDIO update— new Bag Recorder features!

Just pushed some improvements to the bag recorder in ROS2-STUDIO:

Storage format selection — choose between sqlite3 and mcap directly from the GUI
Timed recording — set a duration (in seconds) and the recorder auto-stops when done
Batch/split recording— split bags into chunks every N seconds (--max-bag-duration style)

All options are configurable from the UI without touching the terminal.

:link: GitHub - Sourav0607/ROS2-STUDIO: A comprehensive ROS2 monitoring and management tool with an intuitive GUI for performance monitoring, bag recording, and bag playback. · GitHub

Feedback and contributions welcome!

1 post - 1 participant

Read full topic

by Sourav24 on June 15, 2026 06:38 AM

June 13, 2026
LSEP v0.2 — now a buildable ROS 2 reference implementation: typed lsep_msgs + managed lifecycle node, green CI

Hi everyone,

v0.2.0 now ships a runnable ROS 2 reference implementation — a package you can colcon build in a couple of minutes, with typed messages, a managed lifecycle node, and green CI. Before anything gets locked towards a 1.0, I’d like people who run real robots to poke holes in the architecture.

What’s in v0.2

1. Typed interfaces (lsep_msgs) lsep_msgs/Signal carries the state twice: as a uint8 enum for machines and as a state_name string for debugging via ros2 topic echo. Undefined physics are encoded as NaN instead of magic numbers (e.g. TTC when closing velocity is zero). Light, Sound, and Motion are separate message types — currently nested inside Signal on a single topic. The win today is type reuse and zero parsing overhead versus our previous JSON-in-String transport; whether modalities should get their own topics is an open question I’d like your input on (see below).

2. Managed lifecycle node The core lsep_node is an rclpy.lifecycle.Node following the ROS 2 managed-nodes design, so a safety supervisor can orchestrate configure/activate/deactivate deterministically and keep the signaling layer isolated from the navigation stack.

3. Engine v2.1 — two blind spots in my original state machine, fixed

  • Dwell-based de-escalation: a calmer raw state must remain stable for dwell_de_escalation_s (default 1.5 s) before the machine steps down. Escalation stays immediate; THREAT (< 0.5 s TTC) bypasses everything.
  • Input watchdog: if the sensor stream dies or occludes for longer than input_timeout_s, the reported state degrades to LOW_CONF instead of confidently latching the last danger state on dead sensors.

4. Deprecation policy The old JSON string on /lsep/state is kept as a deprecated mirror behind a publish_json parameter, so nothing existing breaks during migration.

Evidence

CI builds the workspace in a ros:jazzy container on every push, runs the 9/9 unit tests (the engine is testable without a ROS installation), and runs a headless smoke-test where a simulated human approaches, lingers, and retreats. The CI job hard-fails unless the observed state ladder actually reaches THREAT and then de-escalates — so green CI proves the temporal behavior, not just compilation. Ordered ladder from the smoke-test (two full cycles):

INTEGRITY → AWARENESS → INTENT → CARE → CRITICAL → THREAT → AWARENESS → IDLE
          → AWARENESS → INTENT → CARE → CRITICAL → THREAT → AWARENESS
                                            └─ dwell-based de-escalation, 2x ─┘

The THREAT -> AWARENESS step is the one that matters: it shows the dwell timer stepping the robot down only after the danger has genuinely cleared, rather than oscillating.

What I’d genuinely like review on

  1. State representation: uint8 enum + mirror string in one message — sane, or would you model this differently?
  2. NaN convention: NaN in float32 fields for undefined physics — acceptable, or do you prefer explicit validity bools?
  3. QoS: what profile would you expect for a safety-adjacent signaling topic at 10 Hz — reliable vs. best effort, what depth?
  4. Topic layout: one Signal topic with nested modalities (current), or per-modality topics (lsep/light, …) so e.g. an LED driver subscribes only to what it renders?
  5. Naming/namespacing conventions you’d want before this touches real fleets.

Known limits (honestly)

The TTC computation is currently 1D (distance / closing_velocity) — a placeholder; 2D time-to-intercept with trajectory prediction is on the roadmap. No hardware-in-the-loop testing yet — validation so far is the ros:jazzy CI build plus the simulated human, not a physical robot. This is a reference implementation, not certified production safety code.

Repo & 5 minute quick start: GitHub - NemanjaGalic/LSEP: Open protocol for standardized human-robot communication — 9 states, 3 modalities, 1 grammar. Physics-based. EU AI Act ready. · GitHub Release: Release ROS 2 Reference Implementation v0.2.0 · NemanjaGalic/LSEP · GitHub

Thanks for any time you spend tearing this apart.

— Nemanja

3 posts - 2 participants

Read full topic

by NemanjaGalic on June 13, 2026 04:51 PM

Measure serialized topic bandwidth and serialization times using ros_babel_fish_tools

Greetings fellow roboticists :rocket:,

I’ve just added something to ros_babel_fish which could be useful to you as well.
I wanted to know how much bandwidth the serialized message on a topic consumes and how much latency the serialization adds.
So, ros_babel_fish_tools got a new tool: stats
While the existing echo allows subscribing to any topic and outputs the message content as JSON or yaml, the new stats also subscribes to any topic, measures the received rate, message latency (if the message has a header and requires synchronized clocks), the deserialization time from serialized message to ROS message, and the bandwidth (based on the size of the serialized message).
Additionally, you can check how compression of the serialized message would affect the bandwidth and latency.
For example, for our filtered point cloud, it makes quite a difference, but for our compressed wide-angle image, it is wasted time.

Thank you for reading to the end, here’s your image:

1 post - 1 participant

Read full topic

by StefanFabian on June 13, 2026 12:58 PM

June 12, 2026
ROS2 multi-machine with WSL2 + Linux (NVIDIA Jetson Orin Nano)

Hi everyone,

I am trying setting up a ROS2 multi-machine communication between WSL2 (Ubuntu 22.04) running on a local Windows11 machine and a Linux-native platform, i.e., NVIDIA Jetson Orin Nano. My objective is generating data on my WSL2 machine, and process them using the Jetson.

However, I am experiencing issues related to the fact that the two machines cannot see the respective topics / nodes. They are both connected to the same network, and I have activated the mirroring mode networking in WSL2 using the flag networkingMode=mirrored. I have also tried using Cyclone and FastDDS using custom configuration files, but nothing worked so far.

I was thus wondering if anybody has succeded in setting up a proper ROS2 communication between WSL2 and a native Linux machine. If you need any further information, please do not hesitate to ask.

Thank you in advance!

Simone

4 posts - 2 participants

Read full topic

by simonespring on June 12, 2026 11:24 PM

STS3215 pan/tilt + LD19: a no-SDK 3D scanning module for ROS 2 Jazzy

I put together a small ROS 2 subsystem that turns a 2-DOF pan/tilt platform and a cheap 2D LiDAR into a stop-and-capture 3D scanner, and figured it might be useful to someone else here.

The setup: two Feetech STS3215 serial-bus servos aim an LDROBOT LD19. A node sweeps the platform and an assembler stacks the 2D scans into a `PointCloud2` using the live TF tree. There’s an optional MQTT bridge so an external controller (in my case a microcontroller mission queue on a rover) can trigger scans and get a completion handshake back.

It’s a *complete* project — it even includes a fix to the LiDAR driver (upstream `ldlidar_stl_ros2` won’t build on recent GCC/glibc; the patched fork is linked below). It talks to the rover over a well-defined set of MQTT messages, but every command also has an equivalent ROS 2 topic, so if you want a pure ROS 2 setup you just don’t launch the bridge. (Personally I love the MQTT side — it lets me drive the whole thing from a tablet.)

No vendor SDK — the Feetech STS/SMS half-duplex protocol is implemented directly over pyserial, including handling the URT-1 adapter’s habit of echoing every TX byte back on the RX line (the kind of thing that eats an evening if you don’t know it’s coming). The assembler is driver-agnostic: it consumes standard `sensor_msgs/LaserScan` on `/scan`, so any conformant 2D LiDAR should work.

It’s running on an RK3588 today and is built to go headless on a Pi 5.

This is the first piece I’m open-sourcing from a larger autonomous rover project, GPL-3.0. I’d genuinely welcome feedback — particularly from anyone who’s done multi-LiDAR or TF-timing work, since the scan-to-TF synchronization was the fussiest part to get right. But it does work! Happy to answer questions about any of it.

- Project: GitHub - aa2mz/pan_tilt_lidar: A ROS 2 (Jazzy) subsystem that turns a 2-DOF pan/tilt platform and a 2D LiDAR into a 3D scanner. · GitHub

- Patched LiDAR driver: GitHub - aa2mz/ldlidar_stl_ros2: LDROBOT DTOF LiDAR ROS2 Package · GitHub

5 posts - 3 participants

Read full topic

by Gizmo on June 12, 2026 05:32 PM

Isaac Lab Teleoperation & Data Collection 2.0: Automated Block Stacking Dataset Generation with AgileX PiPER and NERO

0.Introduction

In our previous article, we introduced a complete teleoperation and demonstration collection pipeline for the AgileX NERO and PiPER robotic arms in Isaac Lab.

The project demonstrated how to:

  • Import custom robot assets into Isaac Lab

  • Configure Differential IK control

  • Perform keyboard-based teleoperation

  • Record demonstrations into HDF5 datasets

  • Replay trajectories for verification

:backhand_index_pointing_right: Previous article:

Isaac Lab Teleoperation & Data Collection: Controlling the AgileX NERO 7-DoF Robotic Arm

:backhand_index_pointing_right: Repository: The full source code is available in the repository.

While teleoperation is an effective approach for collecting expert demonstrations, scaling datasets through manual operation quickly becomes time-consuming and difficult to maintain consistently.

In this follow-up work, we extend the original pipeline with an automated block stacking data generation framework that can autonomously complete manipulation tasks and continuously export successful demonstrations.

What’s New in this Tutorial ?

Compared with the previous teleoperation workflow, this version introduces:

:white_check_mark: Automated trajectory generation

:white_check_mark: Autonomous block stacking execution

:white_check_mark: Differential IK-based task completion

:white_check_mark: PCHIP trajectory smoothing

:white_check_mark: Automatic success detection

:white_check_mark: Continuous dataset generation

:white_check_mark: Zero human intervention during collection

The result is a scalable demonstration generation pipeline suitable for robot learning workflows.

1.Systerm Architecture

The overall workflow is shown below:


Cube Position Detection

          ↓

Trajectory Planner

          ↓

IK Stack Controller

          ↓

Differential IK (Isaac Lab)

          ↓

Robot Motion

          ↓

Recorder Manager

          ↓

HDF5 Dataset Export

Unlike the teleoperation workflow, no human operator is involved once the simulation starts.

The controller generates Cartesian target motions, while Isaac Lab’s built-in Differential IK module handles inverse kinematics computation internally.

2.Implementation Steps

Step 1.Automated Block Stacking Controller

A new script was introduced:

scripts/tools/record_ik_stack.py

The controller executes a complete stacking sequence:


Approach Cube

      ↓

Descend

      ↓

Grasp

      ↓

Lift

      ↓

Move To Target

      ↓

Place

      ↓

Retreat

Instead of commanding joint angles directly, the controller outputs Cartesian pose increments:

action = [ Δx,Δy,Δz,Δrx,Δry,Δrz,gripper ]

This design allows the same controller logic to be reused across different robot configurations.

The newly introduced block stacking controller is shown below.

sequence = [
    (self._shift_z(src, self.Z_PRE), self.GRIP_OPEN_VAL, 30),
    (self._shift_z(src, self.Z_DOWN), self.GRIP_OPEN_VAL, 30),
    (self._shift_z(src, self.Z_DOWN), self.GRIP_CLOSE_VAL, 30),
    (self._shift_z(src, self.Z_LIFT), self.GRIP_CLOSE_VAL, 30),
    (self._shift_z(place_pos, self.Z_PRE), self.GRIP_CLOSE_VAL, 30),
    (self._shift_z(place_pos, self.Z_PLACE), self.GRIP_CLOSE_VAL, 30),
    (self._shift_z(place_pos, self.Z_PLACE), self.GRIP_OPEN_VAL, 20),
    (self._shift_z(place_pos, self.Z_RETREAT), self.GRIP_OPEN_VAL, 30),
]

Code shown below to realize this step

def plan(self) -> None:
    """plan the full pick-and-place trajectory."""
    cube_height = self.cubes[0].data.root_pos_w[:, 2:3].clone(2.0

    all_waypoints: list[torch.Tensor] = []
    all_gripper_vals: list[float] = []
    all_durations: list[int] = []

    base_pos = self.cubes[0].data.root_pos_w.clone()

    for i in range(1, len(self.cubes)):
        src = self.cubes[i].data.root_pos_w.clone()
        place_pos = self._shift_z(base_pos, cube_height)

        sequence = [
            (self._shift_z(src, self.Z_PRE), self.GRIP_OPEN_VAL, 30),
            (self._shift_z(src, self.Z_DOWN), self.GRIP_OPEN_VAL, 30),
            (self._shift_z(src, self.Z_DOWN), self.GRIP_CLOSE_VAL, 30),
            (self._shift_z(src, self.Z_LIFT), self.GRIP_CLOSE_VAL, 30),
            (self._shift_z(place_pos, self.Z_PRE), self.GRIP_CLOSE_VAL, 30),
            (self._shift_z(place_pos, self.Z_PLACE), self.GRIP_CLOSE_VAL, 30),
            (self._shift_z(place_pos, self.Z_PLACE), self.GRIP_OPEN_VAL, 20),
            (self._shift_z(place_pos, self.Z_RETREAT), self.GRIP_OPEN_VAL, 30),
        ]

        for wp, gv, dur in sequence:
            all_waypoints.append(wp.squeeze(0))
            all_gripper_vals.append(gv)
            all_durations.append(dur)

        all_durations[-1] += 30
        base_pos = self._shift_z(base_pos, cube_height)

    # Generate smooth trajectory
    self._path = self._generate_smooth_trajectory(all_waypoinall_durations)

    # Expand gripper timeline
    self._gripper_timeline = []
    for gv, dur in zip(all_gripper_vals, all_durations):
        self._gripper_timeline.extend([gv] * dur)

    self._total_steps = self._path.shape[0]
    self._gripper_timeline = self._gripper_timeline[:s_total_steps]

    self._step_idx = 0
    self._post_traj_wait_count = 0
    self._last_gripper_val = self.GRIP_OPEN_VAL

    print(f"[IKStackController] Trajectory planned: {s_total_steps} steps")

Step 2.Differential IK-Based Control

Rather than solving inverse kinematics explicitly, the controller outputs Cartesian pose increments and delegates IK computation to Isaac Lab’s built-in Differential IK controller.

def compute_action(self, env: gym.Env) -> torch.Tensor:
    """Return the action for the current step.
    Returns:
        torch.Tensor of shape ``(1, 7)``:
            ``[Δx, Δy, Δz, Δrx, Δry, Δrz, gripper_binary]``
    """
    # trajectory exhausted → hold position
    if self._step_idx >= self._total_steps:
        return torch.tensor(
            [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, self_last_gripper_val]],
            device=env.device,
        )
    # delta position = target − current
    target_pos = self._path[self._step_idx]  # [1, 3]
    current_pos = self.ee_frame.data.target_pos_w[:, 0, :]  # [1,3]
    delta_pos = target_pos - current_pos  # [1, 3]
    # delta rotation = zero (keep orientation)
    delta_rot = torch.zeros(1, 3, device=env.device)
    # gripper binary command
    gv = self._gripper_timeline[self._step_idx]
    grip_tensor = torch.tensor([[gv]], device=env.device)
    action = torch.cat([delta_pos, delta_rot, grip_tensor],dim=-1)  # (1, 7)
    self._step_idx += 1
    self._last_gripper_val = gv
    return action

Step 3.Trajectory Smoothing with PCHIP

One challenge during autonomous execution is trajectory discontinuity between waypoints.

To improve motion quality, the controller uses Hermite-based interpolation with PCHIP-style tangents to generate smooth Cartesian trajectories.

@staticmethod
def _generate_smooth_trajectory(
    waypoints: list[torch.Tensor], durations: list[int]
) -> torch.Tensor:
    """Interpolate waypoints with PCHIP-style tangents.
    Args:
        waypoints: list of ``(3,)`` tensors.
        durations: steps per segment (``len == len(waypoints) -1``).
    Returns:
        ``torch.Tensor`` of shape ``(Total, 1, 3)``.
    """
    n = len(waypoints)
    wp = torch.stack(waypoints)  # [N, 3]
    tangents = torch.zeros_like(wp)
    if n > 1:
        tangents[0] = wp[1] - wp[0]
        tangents[-1] = wp[-1] - wp[-2]
        for i in range(1, n - 1):
            tangents[i] = (wp[i + 1] - wp[i - 1]) / 2.0
    segments = []
    for i in range(n - 1):
        p0, p1 = wp[i], wp[i + 1]
        m0, m1 = tangents[i], tangents[i + 1]
        steps = durations[i]
        if torch.norm(p1 - p0) < 1e-4:
            seg = p0.unsqueeze(0).repeat(steps, 1)
        else:
            s = torch.linspace(0, 1, steps, device=p0.device)unsqueeze(-1)
            h00 = 2 * s**3 - 3 * s**2 + 1
            h10 = s**3 - 2 * s**2 + s
            h01 = -2 * s**3 + 3 * s**2
            h11 = s**3 - s**2
            seg = h00 * p0 + h10 * m0 + h01 * p1 + h11 * m1
        segments.append(seg)
    return torch.cat(segments, dim=0).unsqueeze(1)  # [Total, 1, 3]

Step 4.Automatic Success Verification

To ensure dataset quality, demonstrations are exported only after the stacking task is successfully completed.

The workflow continuously evaluates the stacking success condition:

Failed trajectories are automatically discarded.

def process_success_condition(env: gym.Env, success_term: object | None, success_step_count: int) -> tuple[int, bool]:
    if success_term is None:
        return success_step_count, False
    if bool(success_term.func(env, **success_term.params)[0]):
        success_step_count += 1
        if success_step_count >= args_cli.num_success_steps:
            env.recorder_manager.record_pre_reset([0], force_export_or_skip=False)
            env.recorder_manager.set_success_to_episodes(
                [0], torch.tensor([[True]], dtype=torch.bool, device=env.device)
            )
            env.recorder_manager.export_episodes([0])
            print("Success condition met! Recording completed.")
            return success_step_count, True
    else:
        success_step_count = 0
    return success_step_count, False

Step 5.Dataset Export

Successful demonstrations are exported automatically using Isaac Lab’s Recorder Manager.

Output format:

ik_dataset.hdf5

The exported datasets contain:

  • Observations

  • Actions

  • Episode Metadata

  • Success Labels

and can be directly integrated into:

  • Robomimic

  • LeRobot

  • ACT

  • Diffusion Policy

  • OpenVLA

with minimal conversion effort.

Step6. Running the Pipeline

  • PiPER
python scripts/tools/record_ik_stack.py \ 
--task Isaac-Stack-Cube-Piper-IK-Rel-v0 \ 
--device cuda \ 
--dataset_file ./datasets/ik_dataset.hdf5 \ 
--num_demos 10

piper_ik

  • NERO
python scripts/tools/record_ik_stack.py \ 
--task Isaac-Stack-Cube-Nero-IK-Rel-v0 \ 
--device cuda \ 
--dataset_file ./datasets/ik_dataset.hdf5 \ 
--num_demos 10

nero_ik

3.Why This Matters

This work moves the Isaac Lab workflow from:

Human Demonstration Collection

towards

Automated Demonstration Generation

which is an important step for building large-scale robot learning datasets.

Rather than spending hours manually collecting demonstrations, developers can now generate consistent trajectories automatically and focus on downstream policy training.

FAQ

Q1: Is this solution fully compatible with all Sogot desktop robotic arms?

This solution natively supports the two mainstream Sogot desktop robotic arm models: PiPER and NERO. It features dedicated optimizations for joint limits, movement speed, gripper parameters, and workspace of these two models. You can switch between models directly via corresponding task commands. It is not compatible with other arm models out of the box, but compatibility can be achieved by fine-tuning motion offset parameters and trajectory step lengths.

Q2: How to resolve environment mismatch errors during runtime?

First, confirm you have deployed the IsaacLab base environment with CUDA support, and verify matching versions for PyTorch, gymnasium, and Pinocchio dependencies.

Next, ensure the task parameter in your run command matches your Sogot arm model — PiPER and NERO task commands cannot be interchanged.

Finally, check that GPU acceleration is enabled. This project requires CUDA acceleration for robotic arm inverse kinematics (IK) solving and trajectory computation; running on CPU will throw runtime errors immediately.

Q3: Can the collected datasets be directly used for training physical Sogot robotic arms?

Yes. The datasets output by this solution replicate the physical characteristics of real Sogot arms across motion workspace, control logic, and movement trajectories. Simulation data aligns closely with real hardware control logic, so no extra format conversion is required. The datasets can be directly applied to imitation learning training for physical PiPER and NERO arms, effectively cutting the high cost and operational risks of real-world data sampling.

Q4: How to customize data sample volume and sampling frequency?

Flexible configuration is available via runtime command arguments:

  • num_demos: Sets the total number of successful demonstration samples to collect; set to 0 for infinite loop sampling.

  • step_hz: Adjusts the simulation sampling frequency (default: 30 Hz). Tune this value based on your Sogot arm’s movement speed to match different operation paces.

Q5: What causes stuttering trajectories or stacking failures on the robotic arm?

This issue most often stems from two root causes:

CUDA acceleration is not active, leading to delayed IK solving and choppy trajectory calculations.

Mismatch between simulation environment frame rate and robotic arm motion step length.

Troubleshooting steps: First check your GPU operational status. Then fine-tune PCHIP trajectory interpolation step length and Z-axis offset parameters to meet precise operation requirements for Sogot robotic arms.

:speech_balloon: Have Question?

If you encounter any issues with environment installation, parameter configuration, or RL training, feel free to leave your questions for further discussion.

1 post - 1 participant

Read full topic

by Agilex_Robotics on June 12, 2026 10:53 AM

Invitation to African Robotics Summit 2026 - June 25, 2026

Dear Friends and Colleagues,

On behalf of the African Robotics Network (AfROB), we are delighted to invite you to the AfROB Virtual Summit, taking place on Thursday, June 25, 2026, at 2:00 - 5:00 PM Central African Time (CAT).

This special gathering will mark the launch of our State of Robotics in Africa report and provide an opportunity for our community to reflect, connect, and contribute to shaping the future of robotics and AI across Africa.

Please find the flyer attached, and you are warmly welcome to register and learn more at afrob.org/summit.

Thank you.

1 post - 1 participant

Read full topic

by SimeonOA on June 12, 2026 04:36 AM

June 10, 2026
EpisodeVault: open source tool to debug why your LeRobot model regressed

Been building with LeRobot v3 and kept hitting the same wall: retrain a policy, it gets worse, no clear idea why. DVC tells you files changed. MLflow tells you which run. Nobody tells you which tasks dropped or which episodes degraded between dataset versions.

Built a small open source library to fill that gap.


Four commands

episodevault track ./my_dataset
episodevault commit -m "added kitchen episodes"
episodevault diff v1.0 v2.0
episodevault blame model_v3


What the diff looks like

Ran this against two real LeRobot datasets:

Dataset diff: v1.0 → v2.0
────────────────────────────────────────────────────
Episodes added:    +0
Episodes removed:  -7

Distribution shift:
  factory_pick        2 → 6   ↑ 200%  ⚠️
  kitchen_grasp       4 → 1   ↓ 75%   ⚠️

Quality metrics:
  avg episode length:    3.7s → 3.0s  ↓
  success_rate:          0.88 → 0.38  ↓
  camera_sync_score:     1.00 → 1.00  →

Likely regression cause:
  'kitchen_grasp' episodes dropped 75% (4 → 1). Restore from prior
  version if this task is in your eval benchmark.


The blame command

One line in your training script:

import episodevault as ev
ev.log_training_run(model_version="model_v3", dataset_version="v2.0")

Then later:

episodevault blame model_v3

Traces the model back to the exact dataset version that trained it and shows the diff automatically.


Compatibility

Tested against four real datasets from the Hub:

Robot Dataset Episodes Frames Parse time
aloha aloha_static_pro_pencil 25 8,750 0.35s
aloha aloha_mobile_shrimp 18 67,500 0.38s
so100 svla_so100_stacking 56 22,956 0.63s
aloha aloha_mobile_cabinet 85 127,500 2.73s

Install

pip install episodevault

Python 3.10+. Works on any local LeRobot v3 dataset.


GitHub: Rohan-Prabhakar/EpisodeVault

If you have a dataset where this breaks or gives a wrong regression hint, open an issue, that’s the most useful feedback right now.

2 posts - 1 participant

Read full topic

by Rohan-Prabhakar on June 10, 2026 09:54 PM

June 09, 2026
What's the most annoying part of your ROS 2 workflow?

Been spending more time in ROS 2 lately and getting a feel for the daily grind.

For folks working on real robots, what part of going from code change to seeing it run drives you the most nuts? And have you found anything that actually makes it better?

8 posts - 4 participants

Read full topic

by maxh1t on June 09, 2026 11:28 AM

Logging and Observability Guide Discussion | Cloud Robotics WG Meeting 2026-06-15

Please come and join us for this coming meeting at Mon, Jun 15, 2026 4:00 PM UTCMon, Jun 15, 2026 5:00 PM UTC, where we will be discussing Logging & Observability tips and best practices. The group has been researching in this area for the past several months, so we are preparing to assemble a guide, then invite previous guest speakers to review it before publishing.

Last session, we continued preparing our questions for the State of Cloud Robotics Survey 2026. This survey has now been released and is available here (all developers using the cloud in any way are invited to respond!), so once the survey has received a few more submissions, we will be analysing and discussing the results. If you’re interested in watching the previous session, the meeting recording is available on YouTube.

The meeting link for next meeting is here, and you can sign up to our calendar or our Google Group for meeting notifications or keep an eye on the Cloud Robotics Hub.

Hopefully we will see you there!

1 post - 1 participant

Read full topic

by mikelikesrobots on June 09, 2026 11:23 AM

June 08, 2026
Context & Hardware Setup

I am developing an Autonomous Mobile Robot (AMR) designed to operate inside a dairy cattle barn. The locomotion system suffers from high wheel slippage due to the environment’s muddy, uneven, and unpaved terrain.

My current sensor suite consists of:

  • Wheel Encoders (Differential drive odometry)

  • IMU (Providing angular velocity and absolute orientation)

  • Ultra-Wideband (UWB) Positioning System (1 Tag on the robot, multiple Anchors around the barn)

  • (Upcoming) Range sensors for obstacle avoidance (cattle detection).

For state estimation, I am using the robot_localization package running a Dual EKF setup to broadcast the map -> odom -> base_footprint transform tree.

The Problem

I am experiencing significant localization inaccuracies, specifically with the robot’s heading (yaw) and absolute positioning.

  1. Heading / Yaw Errors: The estimated orientation is frequently wrong or drifts rapidly. The barn structure contains dense steel pillars (headlocks and support frames) which I suspect are causing massive magnetic disturbances to the IMU’s magnetometer.

  2. UWB Inaccuracy & Jumps: Initially, the UWB update rate was 1 Hz, which caused huge position jumps. I managed to optimize the anchor-tag communication to achieve around 10 Hz. While the frequency issue is resolved, I still observe positional errors between 10 cm to 40 cm, which drastically degrade especially when the robot approaches the steel pillars or when cattle walk between the tag and the anchors.

Current Configuration & Attempts

  • The first EKF instance fuses wheel odometry and IMU (angular velocity) to output continuous odom -> base_footprint.

  • The second EKF instance fuses the odom data and absolute UWB positions to output map -> odom.

  • I have noticed that high multipath reflections and Non-Line-of-Sight (NLOS) conditions caused by steel structures and livestock are heavily corrupting the UWB ranges.

Questions for the Community

Given the harsh environment (muddy terrain, heavy metal interference, dynamic obstacles like cows), I would highly appreciate any advice or architectural suggestions:

  1. How should I handle the IMU in a high-metal environment? Should I completely disable the magnetometer fusion in robot_localization and rely solely on differential_drive odometry + IMU angular velocity (z-axis gyro) for heading, even if it drifts over time?

  2. How can I mitigate UWB multipath/NLOS errors in EKF? Are there proven outlier rejection methods or thresholding techniques within robot_localization (like using the covariance matrix dynamically or mahalanobis_distance filtering) to ignore bad UWB fixes near the steel pillars?

  3. Alternative Fusion Strategies: Has anyone successfully implemented robust UWB-based localization in similar agricultural setups? Would moving towards an Unscented Kalman Filter (UKF) or a factor-graph-based optimization approach (like Ceres or GTSAM) yield better results than standard Dual EKF?

Thank you in advance for your insights!

My ekf configuration :

odometry_ekf_filter_node:
    ros__parameters:
        sensor_timeout: 0.1
# The frequency, in Hz, at which the filter will output a position estimate. Note that the filter will not begin
# computation until it receives at least one message from one of the inputs. It will then run continuously at the
# frequency specified here, regardless of whether it receives more measurements. Defaults to 30 if unspecified.
        frequency: 30.0
        permit_corrected_publication: false
# ekf_localization_node and ukf_localization_node both use a 3D omnidirectional motion model. If this parameter is
# set to true, no 3D information will be used in your state estimate. Use this if you are operating in a planar
# environment and want to ignore the effect of small variations in the ground plane that might otherwise be detected
# by, for example, an IMU. Defaults to false if unspecified.
        two_d_mode: true

# Use this parameter to provide an offset to the transform generated by ekf_localization_node. This can be used for
# future dating the transform, which is required for interaction with some other packages. Defaults to 0.0 if
# unspecified.
        transform_time_offset: 0.0

# Use this parameter to provide specify how long the tf listener should wait for a transform to become available.
# Defaults to 0.0 if unspecified.
        transform_timeout: 0.05

# If you're having trouble, try setting this to true, and then echo the /diagnostics_agg topic to see if the node is
# unhappy with any settings or data.
        print_diagnostics: false

# Whether to publish the acceleration state. Defaults to false if unspecified.
        publish_acceleration: false

# Whether to broadcast the transformation over the /tf topic. Defaults to true if unspecified.
        publish_tf: true
        # initial_state: [2.0,  -8.0,  0.0,
        #             0.0,  0.0,  0.0,
        #             0.0,  0.0,  0.0,
        #             0.0,  0.0,  0.0,
        #             0.0,  0.0,  0.0]
# 1. Set the map_frame, odom_frame, and base_link frames to the appropriate frame names for your system.
#     1a. If your system does not have a map_frame, just remove it, and make sure "world_frame" is set to the value of odom_frame.
# 2. If you are fusing continuous position data such as wheel encoder odometry, visual odometry, or IMU data, set "world_frame"
#    to your odom_frame value. This is the default behavior for robot_localization's state estimation nodes.
# 3. If you are fusing global absolute position data that is subject to discrete jumps (e.g., GPS or position updates from landmark
#    observations) then:
#     3a. Set your "world_frame" to your map_frame value
#     3b. MAKE SURE something else is generating the odom->base_link transform. Note that this can even be another state estimation node
#         from robot_localization! However, that instance should *not* fuse the global data.
        #map_frame: map              # Defaults to "map" if unspecified
        odom_frame: odom            # Defaults to "odom" if unspecified
        base_link_frame: base_footprint  # Defaults to "base_link" if unspecified
        world_frame: odom           # Defaults to the value of odom_frame if unspecified

        odom0: /wheel_odom
        # x, y pozisyon differential modda fuse ediliyor.
        # Twist (vx) kapalı - twist verisine güvenilmiyor.
        # differential: true -> mutlak pose değil, adım adım delta alınıyor,
        # böylece birikimli odometri hatası direkt EKF'e yansımaz.
        odom0_config: [true, true, false,
                      false, false, false,
                      false, false, false,
                      false, false, false,
                      false, false, false]
        odom0_differential: true
        odom0_relative: false
        odom0_queue_size: 10
        odom0_pose_rejection_threshold: 5.0
        odom0_twist_rejection_threshold: 5.0

        imu0: /witmotion/imu
        imu0_config: [false, false, false,
                      false, false, false,  # absolute yaw KAPALI — motor EMI manyetometreyi bozuyor, gradual drift yaratıyor
                      false, false, false,
                      false, false, true,   # yaw_rate (jiroskop) — SLAM map→odom global yaw'ı düzeltiyor
                      false, false, false]
        imu0_differential: false
        imu0_relative: false
        imu0_queue_size: 10
        # EMI koruması: 10.0'dan 3.0'a düşürüldü.
        # Motorlardan kaynaklanan magnetik spike'lar bu eşiği aşarsa elenir.
        # Eğer yaw sürekli reddediliyorsa (diagnostics'te görünür) değeri
        # biraz artır (4.0-5.0) ya da imu0_config[5]'i false yap.
        imu0_pose_rejection_threshold: 3.0
        imu0_twist_rejection_threshold: 5.0
        imu0_linear_acceleration_rejection_threshold: 0.8
        imu0_remove_gravitational_acceleration: true
ekf_filter_node:
  ros__parameters:
    frequency: 10.0
    sensor_timeout: 0.5
    two_d_mode: true          # Set false for 3D navigation (drones, etc.)
    transform_time_offset: 0.0
    transform_timeout: 0.0
    print_diagnostics: true
    debug: false
    publish_tf: false

    map_frame: map
    odom_frame: odom
    base_link_frame: base_footprint
    world_frame: map
    # [x, y, z, roll, pitch, yaw, vx, vy, vz, vroll, vpitch, vyaw, ax, ay, az]
    # initial_state: [2.55,  -7.4,  0.0,
    #                 0.0,  0.0,  0.0,
    #                 0.0,  0.0,  0.0,
    #                 0.0,  0.0,  0.0,
    #                 0.0,  0.0,  0.0]
    # -----------------------------------------------------------------------
    # Sensor 0: Wheel Odometry
    # -----------------------------------------------------------------------
    # Fuse: vx, vy, vyaw (velocities from wheel encoders)
    # [x, y, z, roll, pitch, yaw, vx, vy, vz, vroll, vpitch, vyaw, ax, ay, az]
    odom0: /wheel_odom
    # x, y pozisyon differential modda fuse ediliyor.
    # Twist (vx) kapalı - twist verisine güvenilmiyor.
    # differential: true -> mutlak pose değil, adım adım delta alınıyor,
    # böylece birikimli odometri hatası direkt EKF'e yansımaz.
    odom0_config: [true, true, false,
                  false, false, false,
                  false, false, false,
                  false, false, false,
                  false, false, false]
    odom0_differential: true
    odom0_relative: false
    odom0_queue_size: 10
    odom0_pose_rejection_threshold: 5.0
    odom0_twist_rejection_threshold: 5.0

    imu0: /witmotion/imu
    imu0_config: [false, false, false,
                  false, false, false,  # absolute yaw KAPALI — motor EMI manyetometreyi bozuyor, gradual drift yaratıyor
                  false, false, false,
                  false, false, true,   # yaw_rate (jiroskop) — SLAM map→odom global yaw'ı düzeltiyor
                  false, false, false]
    imu0_differential: false
    imu0_relative: false
    imu0_queue_size: 10
    # EMI koruması: 10.0'dan 3.0'a düşürüldü.
    # Motorlardan kaynaklanan magnetik spike'lar bu eşiği aşarsa elenir.
    # Eğer yaw sürekli reddediliyorsa (diagnostics'te görünür) değeri
    # biraz artır (4.0-5.0) ya da imu0_config[5]'i false yap.
    imu0_pose_rejection_threshold: 3.0
    imu0_twist_rejection_threshold: 0.8
    imu0_linear_acceleration_rejection_threshold: 0.8
    imu0_remove_gravitational_acceleration: true

    # -----------------------------------------------------------------------
    # Sensor 1: UWB Trilateration Position
    # -----------------------------------------------------------------------
    # Fuse: x, y, z (absolute position from trilateration)
    # [x, y, z, roll, pitch, yaw, vx, vy, vz, vroll, vpitch, vyaw, ax, ay, az]
    pose0: /uwb/pose
    pose0_config: [true,  true,  false,
                   false, false, false,
                   false, false, false,
                   false, false, false,
                   false, false, false]
    pose0_differential: false
    pose0_queue_size: 10
    pose0_rejection_threshold: 1.0  # Reject UWB jumps > 2m (NLOS protection)

1 post - 1 participant

Read full topic

by Ali_Altin on June 08, 2026 04:26 PM


Powered by the awesome: Planet