Service Client Demo (Python)
This tutorial shows how to integrate ROS 2 services into a YASMIN
state machine using the ServiceState class. Services in
ROS 2 provide request-response communication - ideal for computations,
queries, or triggering specific actions that complete quickly. Unlike
actions, services don't provide feedback during execution and can't be
canceled. We'll create a state that calls the
AddTwoInts service to demonstrate the complete workflow:
preparing a request from blackboard data, calling the service, and
processing the response.
Prerequisites
You need a ROS 2 service server running. Start the example
AddTwoInts service in a separate terminal:
ros2 run yasmin_demos add_two_ints_server
1. Create the ServiceState
Create a custom state that inherits from ServiceState and
implements request creation and response handling. The constructor
takes five parameters: the service type (AddTwoInts), the
service name ("/add_two_ints"), a callback to create the
request, additional custom outcomes (beyond the default SUCCEED and
ABORT), and a response handler. The
create_request_handler method retrieves integers
a and b from the blackboard and populates a
service request object. The response_handler is called
when the service returns, extracts the sum from the response, stores
it in the blackboard, and returns "outcome1" to signal
completion. The ServiceState automatically handles service
availability checking and error scenarios.
from yasmin_ros import ServiceState
from example_interfaces.srv import AddTwoInts
class AddTwoIntsState(ServiceState):
"""
A state that calls the AddTwoInts service to add two integers.
This class is a state in a finite state machine that sends a request
to the AddTwoInts service, retrieves the response, and updates the
blackboard with the result.
Attributes:
service_type (type): The service type being used (AddTwoInts).
service_name (str): The name of the service.
outcomes (list): The list of possible outcomes for this state.
"""
def __init__(self) -> None:
"""
Initializes the AddTwoIntsState.
Calls the parent constructor with the specific service type,
service name, request handler, outcomes, and response handler.
"""
super().__init__(
AddTwoInts, # srv type
"/add_two_ints", # service name
self.create_request_handler, # cb to create the request
["outcome1"], # outcomes. Includes (SUCCEED, ABORT)
self.response_handler, # cb to process the response
)
def create_request_handler(self, blackboard: Blackboard) -> AddTwoInts.Request:
"""
Creates the service request from the blackboard data.
Args:
blackboard (Blackboard): The blackboard containing the input values.
Returns:
AddTwoInts.Request: The request object populated with values from the blackboard.
"""
req = AddTwoInts.Request()
req.a = blackboard["a"]
req.b = blackboard["b"]
return req
def response_handler(
self, blackboard: Blackboard, response: AddTwoInts.Response
) -> str:
"""
Processes the response from the AddTwoInts service.
Updates the blackboard with the sum result from the response.
Args:
blackboard (Blackboard): The blackboard to update with the sum.
response (AddTwoInts.Response): The response from the service call.
Returns:
str: The outcome of the operation, which is "outcome1".
"""
blackboard["sum"] = response.sum
return "outcome1"
2. Create Helper States
Create callback states to set input values and print the result. The
set_ints function initializes the blackboard with two
integers (a=10, b=5) that will be used as inputs for the service call.
The print_sum function retrieves and logs the computed
sum from the blackboard after the service completes. Both functions
return SUCCEED to indicate successful execution. These
simple callback functions demonstrate how to use
CbState for straightforward operations that don't require
a full custom state class - perfect for data preparation, logging, or
simple conditional checks.
from yasmin import CbState
from yasmin_ros.basic_outcomes import SUCCEED
def set_ints(blackboard: Blackboard) -> str:
"""
Sets the integer values in the blackboard.
This function initializes the blackboard with two integer values to be added.
Args:
blackboard (Blackboard): The blackboard to update with integer values.
Returns:
str: The outcome of the operation, which is SUCCEED.
"""
blackboard["a"] = 10
blackboard["b"] = 5
return SUCCEED
def print_sum(blackboard: Blackboard) -> str:
"""
Logs the sum value from the blackboard.
This function retrieves the sum from the blackboard and logs it.
Args:
blackboard (Blackboard): The blackboard from which to retrieve the sum.
Returns:
str: The outcome of the operation, which is SUCCEED.
"""
yasmin.YASMIN_LOG_INFO(f"Sum: {blackboard['sum']}")
return SUCCEED
3. Build the State Machine
Assemble the states into a state machine that orchestrates the
complete service call workflow. The state machine initializes ROS 2,
creates three states with defined transitions, and sets up the YASMIN
viewer for real-time visualization. The
SETTING_INTS state prepares the input data and
transitions to ADD_TWO_INTS on success. The
ADD_TWO_INTS service state can produce three outcomes:
"outcome1" if the service returns successfully
(transitioning to PRINTING_SUM), SUCCEED if
completed without errors, or ABORT if the service fails
(both terminating with "outcome4"). Finally,
PRINTING_SUM logs the result and transitions to the final
outcome. This sequential flow ensures proper data preparation, service
execution, and result handling with graceful error management.
def main() -> None:
yasmin.YASMIN_LOG_INFO("yasmin_service_client_demo")
# Init ROS 2
rclpy.init()
# Set ROS 2 logs
set_ros_loggers()
# Create a FSM
sm = StateMachine(outcomes=["outcome4"])
# Add states
sm.add_state(
"SETTING_INTS",
CbState([SUCCEED], set_ints),
transitions={SUCCEED: "ADD_TWO_INTS"},
)
sm.add_state(
"ADD_TWO_INTS",
AddTwoIntsState(),
transitions={
"outcome1": "PRINTING_SUM",
SUCCEED: "outcome4",
ABORT: "outcome4",
},
)
sm.add_state(
"PRINTING_SUM",
CbState([SUCCEED], print_sum),
transitions={
SUCCEED: "outcome4",
},
)
# Publish FSM info
viewer = YasminViewerPub(sm, "YASMIN_SERVICE_CLIENT_DEMO")
# Execute 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()
4. Run the Demo
First, start the service server that provides the integer addition functionality:
ros2 run yasmin_demos add_two_ints_server
Then run the client demo to execute the state machine that calls the service and displays the result:
ros2 run yasmin_demos service_client_demo.py
Expected Output
The state machine will:
- Set
a = 10andb = 5 - Call the
/add_two_intsservice - Print the sum:
Sum: 15
YASMIN