ROS Parameters Demo (Python)

This tutorial shows how to use ROS 2 parameters within a YASMIN state machine using the GetParametersState. Parameters allow you to configure your state machine behavior at runtime without modifying code. This is essential for creating flexible, reusable robot behaviors that can be adapted to different scenarios, robots, or environments simply by changing parameter values. Instead of hardcoding values like loop counts, thresholds, or names, you can expose them as parameters that can be set via launch files, command-line arguments, or parameter files.

Background

ROS 2 parameters provide a way to:

1. Create Custom States

Define states that use parameters from the blackboard. Notice how FooState retrieves max_counter and counter_str from the blackboard instead of using hardcoded values. This makes the state's behavior configurable - you can change how many iterations it performs or what prefix it uses in log messages without modifying the state's code. The same state class can be used in different state machines with different parameter values, promoting reusability. BarState simply logs the formatted string, demonstrating how parameter-driven data flows through the state machine naturally.

class FooState(State):
    """
    Represents the Foo state in the state machine.

    Attributes:
        counter (int): Counter to track the number of executions of this state.
    """

    def __init__(self) -> None:
        """
        Initializes the FooState instance, setting up the outcomes.

        Outcomes:
            outcome1: Indicates the state should continue to the Bar state.
            outcome2: Indicates the state should finish execution and return.
        """
        super().__init__(["outcome1", "outcome2"])
        self.counter = 0

    def execute(self, blackboard: Blackboard) -> str:
        """
        Executes the logic for the Foo state.

        Args:
            blackboard (Blackboard): The shared data structure for states.

        Returns:
            str: The outcome of the execution, which can be "outcome1" or "outcome2".

        Raises:
            Exception: May raise exceptions related to state execution.
        """
        yasmin.YASMIN_LOG_INFO("Executing state FOO")
        time.sleep(3)  # Simulate work by sleeping

        if self.counter < blackboard["max_counter"]:
            self.counter += 1
            blackboard["foo_str"] = f"{blackboard['counter_str']}: {self.counter}"
            return "outcome1"
        else:
            return "outcome2"

class BarState(State):
    """
    Represents the Bar state in the state machine.
    """

    def __init__(self) -> None:
        """
        Initializes the BarState instance, setting up the outcome.

        Outcomes:
            outcome3: Indicates the state should transition back to the Foo state.
        """
        super().__init__(outcomes=["outcome3"])

    def execute(self, blackboard: Blackboard) -> str:
        """
        Executes the logic for the Bar state.

        Args:
            blackboard (Blackboard): The shared data structure for states.

        Returns:
            str: The outcome of the execution, which will always be "outcome3".

        Raises:
            Exception: May raise exceptions related to state execution.
        """
        yasmin.YASMIN_LOG_INFO("Executing state BAR")
        time.sleep(3)  # Simulate work by sleeping

        yasmin.YASMIN_LOG_INFO(blackboard["foo_str"])
        return "outcome3"

2. Use GetParametersState

Create a state that retrieves ROS parameters and stores them in the blackboard. The GetParametersState is initialized with a dictionary mapping parameter names to default values. When executed, it attempts to retrieve each parameter from the ROS 2 parameter server. If a parameter is set (via command line with --ros-args -p max_counter:=5, in a YAML file, or programmatically), it uses that value. If not found, it falls back to the default value provided in the dictionary. All retrieved parameters are then stored in the blackboard, making them available to subsequent states. This pattern - retrieve configuration before execution - ensures your state machine is properly configured before running and provides clear error handling if required parameters are missing.

from yasmin_ros import GetParametersState
from yasmin_ros.basic_outcomes import SUCCEED, ABORT

rclpy.init()
set_ros_loggers()

sm = StateMachine(outcomes=["outcome4"])

# Add parameter getter as first state
sm.add_state(
    "GETTING_PARAMETERS",
    GetParametersState(
        parameters={
            "max_counter": 3,  # Parameter name with default value
            "counter_str": "Counter",  # Another parameter with default
        },
    ),
    transitions={
        SUCCEED: "FOO",
        ABORT: "outcome4",
    },
)

3. Build the Rest of the FSM

Add the FooState and BarState to complete the state machine. These states use the parameters retrieved by the GetParametersState to control their behavior dynamically.

sm.add_state(
    "FOO",
    FooState(),
    transitions={
        "outcome1": "BAR",
        "outcome2": "outcome4",
    },
)
sm.add_state(
    "BAR",
    BarState(),
    transitions={"outcome3": "FOO"},
)

YasminViewerPub(sm, "YASMIN_PARAMETERS_DEMO")

4. Execute

Run the state machine and handle any interruptions gracefully with proper ROS shutdown procedures.

    # Publish FSM information for visualization
    viewer = YasminViewerPub(sm, "YASMIN_PARAMETERS_DEMO")

    # Execute the FSM
    try:
        outcome = sm()
        yasmin.YASMIN_LOG_INFO(outcome)
    except KeyboardInterrupt:
        if sm.is_running():
            sm.cancel_state()
    finally:
        viewer.cleanup()
        del sm

        # Shutdown ROS 2 if it's running
        if rclpy.ok():
            rclpy.shutdown()


if __name__ == "__main__":
    main()

5. Run with Default Parameters

Run the demo using the default parameter values defined in the code.

ros2 run yasmin_demos parameters_demo.py

This will use the defaults: max_counter=3 and counter_str="Counter".

6. Run with Custom Parameters

Override the default parameter values at runtime using ROS 2 command-line arguments to customize the state machine behavior:

ros2 run yasmin_demos parameters_demo.py --ros-args -p max_counter:=5 -p counter_str:="Iteration"

Expected Behavior

Key Points