-
Notifications
You must be signed in to change notification settings - Fork 376
Dev_Actions
Actions in ROS# provide a mechanism to handle long-running tasks that can be preempted. This is particularly useful for tasks that may take an indeterminate amount of time to complete and need to provide feedback or be canceled. The implementation involves several components that work together to facilitate communication between action clients and servers. This documentation will cover the key components, their interactions, and provide examples of how to implement specific actions.
This page will guide you through the under the hood implementation of actions in ROS# and explain fibonacci action client/server examples. Note that this page only considers ROS2 action implementation.
4.4.1 Roadmap - General Picture 4.4.2 Communication: ROS Bridge Interface 4.4.3 Communicators: Consumers and Providers 4.4.4 RosSocket: Accessing Communicatiors 4.4.5 Abstract Classes
Actions in ROS# are structured in a modular way, distributed over different classes: Abstract class, RosSocket, Communication, Communicator, and finally the actual client/server implementation on a derived class.
Client Diagram:
Server Diagram:
Before diving to the details, in ROS#, every abstact action implementation is designed to be generic. Please mind the notation:
Parameter | Description |
---|---|
TAction |
The ROS 2 action type (e.g., Fibonacci , NavigateToPose ). |
TActionGoal |
The action goal message type interface. (Communication.cs ) |
TActionResult |
The action result message type interface. (Communication.cs ) |
TActionFeedback |
The action feedback message type interface. (Communication.cs ) |
TGoal |
The user defined goal message type (used within TActionGoal ). |
TResult |
The user defined result message type (used within TActionResult ). |
TFeedback |
The user defined feedback message type (used within TActionFeedback ). |
Like any other message in ROS#, actions must be serialized in a way that Ros Bridge can understand. Each message type has its own structure defined by the ROS Design and implemented by the Ros Bridge Protocol. Including actions, ROS# implements these message types in Communication.cs
: Incoming and outgoing messages are deserialized and serialized respectively according to derived `Communication' classes.
The SendActionGoal
class represents a goal request message sent by the client and received by the server.
internal class SendActionGoal<T> : Communication where T : Message
{
public string action { get; set; } // required, the name of the action to send a goal to
public string action_type { get; set; } // required, the action message type
public T args { get; set; } // optional, list of json objects representing the arguments to the service
public bool feedback { get; set; } // optional, if true, sends feedback messages over rosbridge. Defaults to false.
public int fragment_size { get; set; } // optional, maximum size that the result and feedback messages can take before they are fragmented
public string compression { get; set; } // optional, an optional string to specify the compression scheme to be used on messages. Valid values are "none" and "png"
internal SendActionGoal(string id, string action, string action_type, T args, bool feedback = false, int fragment_size = int.MaxValue, string compression = "none") : base(id)
{
this.op = "send_action_goal";
this.id = id;
this.action = action;
this.action_type = action_type;
this.args = args;
this.feedback = feedback;
this.fragment_size = fragment_size;
this.compression = compression;
}
}
By ROS design, both action clients and services have specific functionality. A client not only sends or cancels a goal, but (not necessarily) it is also expected to consume (receive) the server's feedback and result response. Similarly, an action server should also be able to listen to new goal and cancel requests in addition to publishing feedback and result. These server and client behaviors are implemented in Communicatior.cs
: Provider and Consumer respectively.
Below is the generic ActionConsumer
class located in Communicators.cs
. Basically it has two functions: Consuming feedback and result messages sent by the server, and preparing send/cancel target messages. Whenever an action client wants to send or cancel an action, an ActionConsumer
instance is created.
internal ActionConsumer(string id,
string action,
ActionResultResponseHandler<TActionResult> actionResultResponseHandler = null,
ActionFeedbackResponseHandler<TActionFeedback> actionFeedbackResponseHandler = null,
ActionCancelResponseHandler<TActionResult> actionCancelResponseHandler = null)
{
Id = id;
Action = action;
ActionType = GetRosName<TActionResult>().Substring(0, GetRosName<TActionResult>().LastIndexOf("ActionResult"));
ActionResultResponseHandler = actionResultResponseHandler;
ActionFeedbackResponseHandler = actionFeedbackResponseHandler;
ActionCancelResponseHandler = actionCancelResponseHandler;
}
...
The ConsumeResultResponse
, ConsumeFeedbackResponse
and ConsumeCancelResponse
functions deserialize the incoming message from the server (via RosBridge) according to the message interface definitions in the Communication.cs
file. It then triggers the user-defined callback functions (Consume...Response) with the deserialized messages on different threads, which are generic delegate functions passed to the ActionConsumer
constructor when sending or canceling an action.
...
internal override void ConsumeResultResponse(string incomingJsonResultResponse, ISerializer serializer)
{
try
{
ActionResultResponseHandler.Invoke(JsonSerializer.Deserialize<TActionResult>(incomingJsonResultResponse));
}
catch (Exception ex)
{
Console.WriteLine("Error in ConsumeResultResponse: " + ex.Message);
}
}
internal override void ConsumeFeedbackResponse(string incomingJsonFeedbackResponse, ISerializer serializer)
{
try
{
ActionFeedbackResponseHandler.Invoke(JsonSerializer.Deserialize<TActionFeedback>(incomingJsonFeedbackResponse));
}
catch (Exception ex)
{
Console.WriteLine("Error in ConsumeFeedbackResponse: " + ex.Message);
}
}
internal override void ConsumeCancelResponse(string incomingJsonFeedbackResponse, ISerializer serializer)
{
...
Furthermore, SendActionGoalRequest
and CancelActionGoalRequest
are the functions responsible for preparing the send/cancel request messages to be sent. As discussed before, they return an abstract Communication
type that represents the message structure defined by the Ros Bridge protocol. The SendActionGoalRequest
function takes the actual goal message (TActionGoal
type), which is automatically generated by the RosSharp.MessageGeneration
library. On the other hand, CancelActionGoalRequest
does not take a custom cancel action message type, but only takes the id and name of the action to be canceled, as it is much simpler.
...
internal override Communication SendActionGoalRequest<TActionGoal, TGoal>(TActionGoal actionGoal)
{
return new SendActionGoal<TGoal>(
id: Id,
action: actionGoal.action,
action_type: ActionType,
args: actionGoal.args,
feedback: actionGoal.feedback,
fragment_size: actionGoal.fragment_size,
compression: actionGoal.compression
);
}
internal override Communication CancelActionGoalRequest(string frameId, string actionName)
{ ...
The ActionProvider class in Communicators.cs is responsible for handling the server-side logic of ROS actions. It manages the advertisement of actions, listens for incoming goal and cancel requests, and sends feedback and result responses to the clients.
Unlike how services are handled in ROS#, responding is divided into listening and responding for actions. By the ROS design, these functions are blocking and expeced to return quickly. However, action responses are often like that and they require some time to perform the action itself. Thus, Listen...Action
functions transfer the clients' request to the abstract server and returns quickly.
Below is the generic ActionProvider
class. It has several key functions: advertising actions, listening for goal and cancel requests, and sending feedback and result responses. Whenever an action server is initialized, an ActionProvider
instance is created (from RosSocket).
internal abstract class ActionProvider : Communicator
{
internal abstract string Action { get; }
internal abstract Communication RespondResult<TActionResult, TResult>(TActionResult ActionResult)
where TActionResult : ActionResult<TResult>
where TResult : Message;
internal abstract Communication RespondFeedback<TActionFeedback, TFeedback>(TActionFeedback ActionFeedback)
where TActionFeedback : ActionFeedback<TFeedback>
where TFeedback : Message;
internal abstract void ListenSendGoalAction(string message, ISerializer serializer);
internal abstract void ListenCancelGoalAction(string frameId, string actionName, ISerializer serializer);
internal ActionUnadvertisement UnadvertiseAction()
{
return new ActionUnadvertisement(Action);
}
}
The ActionProvider
class has several abstract methods that must be implemented by derived server class. These methods handle the core functionality of the action server:
-
RespondResult:
Sends the result of an action goal to the client. -
RespondFeedback:
Sends periodic feedback about the progress of an action goal to the client. -
ListenSendGoalAction:
Listens for incoming goal requests from clients. -
ListenCancelGoalAction:
Listens for incoming cancel requests from clients.
The ActionProvider<TActionGoal>
class is a concrete implementation of the ActionProvider
class. It handles specific action goals and manages goal reception and feedback/result responses.
internal class ActionProvider<TActionGoal> : ActionProvider
where TActionGoal : Message
{
internal override string Action { get; }
internal SendActionGoalHandler<TActionGoal> SendActionGoalHandler;
internal CancelActionGoalHandler CancelActionGoalHandler;
internal ActionProvider(string action,
SendActionGoalHandler<TActionGoal> sendActionGoalHandler,
CancelActionGoalHandler cancelActionGoalHandler,
out ActionAdvertisement actionAdvertisement)
{
Action = action;
SendActionGoalHandler = sendActionGoalHandler;
CancelActionGoalHandler = cancelActionGoalHandler;
string actionGoalROSName = GetRosName<TActionGoal>();
actionAdvertisement = new ActionAdvertisement(action, actionGoalROSName.Substring(0, actionGoalROSName.LastIndexOf("ActionGoal")));
}
In the constructor of ActionProvider<TActionGoal>
, the action name and handlers for sending goals and canceling goals are initialized.
internal override void ListenSendGoalAction(string message, ISerializer serializer)
{
try
{
SendActionGoalHandler.Invoke(serializer.Deserialize<TActionGoal>(message));
}
catch (Exception ex)
{
Console.WriteLine("Error in ListenSendGoalAction: " + ex.Message);
}
}
internal override void ListenCancelGoalAction(string frameId, string actionName, ISerializer serializer)
{
try
{
CancelActionGoalHandler.Invoke(frameId, actionName);
}
catch (Exception ex)
{
Console.WriteLine("Error in ListenCancelGoalAction: " + ex.Message);
}
}
The ListenSendGoalAction
method deserializes incoming goal requests and invokes the SendActionGoalHandler delegate. Similarly, the ListenCancelGoalAction
method deserializes incoming cancel requests and invokes the CancelActionGoalHandler
delegate.
internal override Communication RespondFeedback<TActionFeedback, TFeedback>(TActionFeedback actionFeedback)
{
return new ActionFeedbackResponse<TFeedback>(
id: actionFeedback.id,
action: actionFeedback.action,
values: actionFeedback.values
);
}
internal override Communication RespondResult<TActionResult, TResult>(TActionResult ActionResult)
{
return new ActionResultResponse<TResult>(
id: ActionResult.id,
action: ActionResult.action,
values: ActionResult.values,
status: ActionResult.status,
result: ActionResult.result
);
}
}
The `RespondFeedback` method creates an `ActionFeedbackResponse` message to send feedback to the client. The `RespondResult` method creates an `ActionResultResponse` message to send the result of an action goal to the client. Again, these messages are not sent directly from the provider class, they are just prepared here to be sent by the `RosSocket`.
The abstract Comminicator
classes are only accessed from RosSocket.cs
Whenever a client sends an action goal, the SendActionGoalRequest
function is called. It gets a unique id for the specific action goal, then calls the ActionConsumer
constructor. Finally, it sends the goal request message from the consumer instance (SendActionGoalRequest
). Sending a goal cancellation request works similarly.
public string SendActionGoalRequest<TActionGoal, TGoal, TActionFeedback, TActionResult>(
TActionGoal actionGoal,
ActionResultResponseHandler<TActionResult> actionResultResponseHandler,
ActionFeedbackResponseHandler<TActionFeedback> actionFeedbackResponseHandler)
where TActionGoal : ActionGoal<TGoal>
where TGoal : Message
where TActionResult : Message
where TActionFeedback : Message
{
string id = GetUnusedCounterID(ActionConsumers, actionGoal.action);
ActionConsumers.Add(id, new ActionConsumer<TActionResult, TActionFeedback>(
id,
actionGoal.action,
actionResultResponseHandler: actionResultResponseHandler,
actionFeedbackResponseHandler: actionFeedbackResponseHandler)
);
Send(ActionConsumers[id].SendActionGoalRequest<TActionGoal, TGoal>(actionGoal));
return id;
}
Although similar, advertising an action has a few more steps compared to the client side. This time, only one consumer instance is created when the action is advertised to the Ros Bridge. Again, a unique id is chosen for the action provider and the ActionProvider' instance is generated. The
RespondFeedback' and `RespondResull' functions perform almost the same as the consumer side sending functions.
public string AdvertiseAction<TActionGoal, TActionFeedback, TActionResult>(
string action,
SendActionGoalHandler<TActionGoal> sendActionGoalHandler,
CancelActionGoalHandler cancelActionGoalHandler)
where TActionGoal : Message
where TActionFeedback : Message
where TActionResult : Message
{
string id = action;
if (ActionProviders.ContainsKey(id))
UnadvertiseAction(id);
ActionAdvertisement actionAdvertisement;
ActionProviders.Add(id, new ActionProvider<TActionGoal>(
action,
sendActionGoalHandler,
cancelActionGoalHandler,
out actionAdvertisement));
Send(actionAdvertisement);
return id;
}
public void RespondFeedback<TActionFeedback, TFeedback>(string id, TActionFeedback actionFeedback)
where TActionFeedback : ActionFeedback<TFeedback>
where TFeedback : Message
{
Send(ActionProviders[id].RespondFeedback<TActionFeedback, TFeedback>(actionFeedback));
}
...
On the other hand, listen and consumer functions are triggered by the Receive
function, again located in RosSocket.cs
. Based on the op
and id
property of the incoming message from the Ros Bridge, the respond/listen function of the matching consumer/provider is triggered.
In the final level of abstraction, we finally implement the basic functionaility of clients and servers in ActionClient.cs
and ActionServer.cs
, respectively.
The ActionClient
class abstracts away the complexities of managing actions in ROS 2 by handling communication with the ROS 2 action server. Users can inherit from this class to define custom actions by implementing specific methods for handling feedback, status, and results.
- Send Goals: Supports sending goals with additional parameters (e.g., feedback, fragment size, compression).
-
Cancel Goals: Allows canceling goals with or without specifying a
frameId
. - Feedback Handling: Provides callback methods for processing feedback from the action server.
- Result Handling: Processes results received from the action server.
- Status Updates: Tracks and updates the current goal status.
-
Inherit from
ActionClient
: Create a custom class that inherits fromActionClient
and specify the action-related types. -
Implement Abstract Methods: Define the methods
SetActionGoal
,OnStatusUpdated
,OnFeedbackReceived
, andOnResultReceived
in the derived class. -
Set Goals: Use
SetActionGoal
to define the action goal with additional parameters such as feedback and compression. -
Send Goals: Call
SendGoal
to send the action goal to the action server. -
Cancel Goals: Use
CancelGoal
to cancel an ongoing goal by providing aframeId
or using the lastframeId
.
These methods must be implemented in the derived class to customize the handling of goals, feedback, status, and results.
Method | Parameters | Description |
---|---|---|
SetActionGoal |
TGoal goal, bool feedback, int fragmentSize, string compression |
Defines the action goal with additional parameters. |
GetActionGoal |
None | Retrieves the configured action goal. |
OnStatusUpdated |
None | Handles updates to the action goal's status. |
OnFeedbackReceived |
None | Processes feedback received from the action server. |
OnResultReceived |
None | Processes the result received from the action server. |
These methods handle core functionality and are not intended to be overridden by the user.
Method | Parameters | Description |
---|---|---|
SendGoal |
None | Sends the current action goal to the action server using RosSocket.SendActionGoalRequest . |
CancelGoal |
string frameId = null |
Cancels the action goal using RosSocket.CancelActionGoalRequest . |
StatusCallback |
GoalStatusArray |
Internal callback for processing status updates from the action server. Passed to Communicators.cs . |
FeedbackCallback |
TActionFeedback |
Internal callback for processing feedback from the action server. Passed to Communicators.cs . |
ResultCallback |
TActionResult |
Internal callback for processing results from the action server. Passed to Communicators.cs . |
The ActionServer
class serves as a base class for creating custom ROS 2 action servers. It simplifies the handling of action goals, cancellations, feedback, and results while abstracting away the underlying communication with the ROS 2 action client.
For more information about server state machine model, see the wiki page.
- Advertise Actions: Automatically sets up the action topic communication with the ROS 2 ecosystem.
- Handle Goals: Processes incoming action goals from the client.
- Manage Cancellations: Handles goal cancellations and transitions to appropriate states.
- Publish Feedback: Sends real-time feedback about the execution status of goals to the client.
- Publish Results: Sends the final result of an action goal to the client.
- State Management: Tracks and updates the internal state of the action server to ensure proper goal execution flow.
-
Inherit from
ActionServer
: Create a custom class that derives fromActionServer
and defines action-specific types. -
Implement Abstract Methods: Override key methods such as
OnGoalReceived
,OnGoalExecuting
,OnGoalCanceling
,OnGoalSucceeded
,OnGoalAborted
, andOnGoalCanceled
to define custom behaviors. -
Initialize the Server: Call
Initialize
to set up communication with the ROS 2 ecosystem. -
Terminate the Server: Call
Terminate
to cleanly shut down the server when it is no longer needed.
These methods must be implemented in the derived class to customize how the server handles different phases of action processing.
Method | Parameters | Description |
---|---|---|
OnGoalReceived |
None | Handles the reception of a new goal from the action client. |
OnGoalExecuting |
None | Handles the transition of a goal into the execution phase. |
OnGoalCanceling |
None | Handles goal cancellation requests from the action client. |
OnGoalSucceeded |
None | Handles the successful completion of a goal. |
OnGoalAborted |
None | Handles the case where the goal execution fails. |
OnGoalCanceled |
None | Handles the cancellation of a goal. |
These methods handle core functionality and should not typically be overridden by the user.
Method | Parameters | Description |
---|---|---|
Initialize |
None | Sets up the action server by advertising the action topic and initializing the state. |
Terminate |
None | Shuts down the action server and unadvertises the action topic. |
SetExecuting |
None | Transitions the goal state to EXECUTING and invokes the OnGoalExecuting method. |
SetSucceeded |
TResult result = null |
Marks the goal as SUCCEEDED and publishes the result. |
SetAborted |
None | Marks the goal as ABORTED and logs the failure. |
SetCanceled |
TResult result = null |
Marks the goal as CANCELED and optionally sets a result. |
GoalCallback |
TActionGoal actionGoal |
Internal callback for handling incoming goals from the client. |
CancelCallback |
string frameId, string action |
Internal callback for handling goal cancellation requests. |
PublishFeedback |
None | Publishes feedback to the action client. |
PublishResult |
None | Publishes the final result of the goal to the action client. |
UpdateAndPublishStatus |
ActionStatus actionStatus |
Updates the server's internal state and publishes it (only ROS1) . |
© Siemens AG, 2017-2025
-
- 1.3.1 R2D2 Setup
- 1.3.2 Gazebo Setup on VM
- 1.3.3 TurtleBot Setup (Optional for ROS2)
- 2.1 Quick Start
- 2.2 Transfer a URDF from ROS to Unity
- 2.3 Transfer a URDF from Unity to ROS
- 2.4 Unity Simulation Scene Example
- 2.5 Gazebo Simulation Scene Example
- 2.6 Fibonacci Action Client
- 2.7 Fibonacci Action Server
- 3.1 Import a URDF on Windows
- 3.2 Create, Modify and Export a URDF Model
- 3.3 Animate a Robot Model in Unity
- 4.1 Introduction to RosBridgeClient
- 4.2 Image Publication
- 4.3 URDF Transfer
- 4.4 Fibonacci Action Client/Server
- Message Handling: Readers & Writers
- Thread Safety for Message Reception
- File Server Package
- ROS-Unity Coordinate System Conversions
- Post Build Events
- Preprocessor Directives in ROS#
- Adding New Message Types
- RosBridgeClient Protocols
- RosBridgeClient Serializers
- Actions in ROS#
- Action Server State Machine Model
© Siemens AG, 2017-2025