Now that we know how to create and use an action server and how to interact with it through the Ros
to command line interface, let's see how to create a C plus plus action client node that use the Fibonacci
action server we created in the last laboratory lesson.
Just as we did for the action server node.
Let's also place the node for the action client within the Arduino CP Examples package.
And so within the source folder, let's create a new file called Simple Action Client Dot CPP.
And here let's start by importing the CPP library.
So let's include the CPP.
And then let's create a new class.
So a new node called Simple Action Client, which inherits from the node class.
So from the CPP namespace from the node class.
And so let's define this function here.
Now let's include all these code.
So let's include this class within our new namespace, as we did also for the action server.
And so let's create a new namespace called as the name of the package.
So Arduino boot CP
examples.
And within this one let's define our class.
So here, let's start by creating the constructor.
So the explicit constructor of the Simple Action Client class and this constructor here receives as
input an object of type node options.
So from RCL, CP, it receives a node options that we call options.
And first.
So when we create an object of the simple action client class, let's start by initializing the node
class.
So the base class and this receives a string as input that is the name that we want to give to the node,
for example, simple action client, and also it eventually receives some node options that we have
stored in the variable options now in order to create the actual action client.
So the one that will communicate with the action server, we need to include another library and this
is the CP action.
So let's include this library and from this library let's create a new object.
So here let's create a new private variable.
So first let's make sure that the constructor is public.
And now among the private variables.
Instead, let's create a new object.
So from the CP Action library that we have just imported, let's use the definition.
So let's create a new object of type client as we saw in the previous Cplusplus laboratory lesson.
To create the action server, we created a new instance of the server class from the CP action here.
Instead to create the action client, we are creating an instance of the client class and so also this
one is a template class to which we need to pass the message interface for the communication with the
action server.
And this is the same interface that we used for the action server.
So the one that is defined in the Arduino bot messages package.
So let's include this interface from the Arduino board messages from the action folder, let's include
the Fibonacci interface.
And so now let's declare that this client is going to use from the Arduino bot messages.
This client from the action is going to use the Fibonacci interface.
Now let's also take a shared pointer.
So let's create a shared pointer to this class, so to the client class, and let's call this variable
client.
Since we are creating a shared pointer, let's also include the usage of the memory library.
So of the memory module of the C plus plus standard library.
And now we can initialize this client variable so we can initialize this shared pointer here within
the constructor and we can use the function.
So from the CP Action library, let's use the function create client also.
This one is a template function and also this one requires that we pass as input the type of the interface.
So once more the Fibonacci interface in order to create a shared pointer to the client object.
And then it also requires.
So in the parentheses here, it requires the instance of the ros2 node that contains the action client.
So actually this is the current object.
So the current class and we refer with the pointer.
This also it receives the name of the action.
So the name of the action server as recognized by Ros2 and this is the Fibonacci Action server.
Now, let's also create another shared pointer, this time to a timer object.
So still among the private variable, this time from the RCL, cpp from the timer base, let's create
a shared pointer and let's call this one timer.
And we already use this object.
So the timer base object in a previous laboratory lesson in order to repeatedly execute a certain function
at regular time intervals.
So let's use this object also here.
So let's initialize it within the constructor and let's use the function create wall timer that we inherited
from the node class.
And now let's also here use using the namespace
Chrono
Literals and using this namespace, let's specify that our wall timer will be executed every second,
so the timer will expire every second.
And now also within the create wall timer function, we also need to set so to define which is the function
that has to be executed every second.
So whenever the timer expires again, let's use the bind function from the standard C plus plus library.
And we want to execute a function called timer callback that we are going to define within the simple
action client class.
So let's indicate that this function is defined or will be defined within the simple action client class.
Then let's also use the pointer this to indicate that we want to use the current object.
So the implementation of this function within the current object.
And then that's all.
So this will not receive any parameters, so we will not receive any input.
Now let's move on.
And among the private methods of this class, let's define this function.
So the timer callback function and actually we don't need this function to be executed repeatedly every
second we need it just to be executed once.
So just the first time.
So whenever we execute it for the first time, let's take the timer object and let's cancel it so that
it will not be executed again.
So basically, with this trick we made possible that the timer callback function is executed only once
and also is executed after one second from the node initialization.
Now, in this function, we just want to use the action client to send a goal to the action server and
also receive the feedback, print the feedback and then print the result message once the server is
done.
However, before sending a goal to the action server, let's first verify that the action server is
actually running and we can use the function.
So on the client object, we can use the function wait for action server.
This function blocks the execution of the client so blocks the execution of this current script until
an action server that is called Fibonacci and that uses the Fibonacci interface becomes available in
Ros two.
So if this one never becomes available.
So let's set that.
It is not available.
Let's print an error message in the terminal.
So with the Http error.
And let's print the message action server not available.
After waiting.
And in this case, let's shut down also the Ros two interface.
So the CP.
Shut down function otherwise.
So if we go outside so here, if the server exists, we can request its execution by sending a new goal
message to it.
So let's start by creating a new goal variable that we call goal message.
And this is an instance of the Arduino board message interface.
And so of the action Fibonacci.
And from this action, let's create an instance of the goal message.
Now, for example, let's request the calculation of the Fibonacci sequence up to the order ten.
So let's set the goal message and the order to be ten.
Then let's also print an informative message.
So here, let's say.
Let's get the CP logger and let's print the message sending goal.
Now to send actually the goal to the action server.
We need to create a new object of the client class.
So let's call this object, send goal options.
And this is an instance of the Xcp action package of the client object.
And this is a template function.
And also in this case, we need to use the Fibonacci.
So here we need to pass.
The Fibonacci interface that the client has to use in order to communicate with the server and from
this class.
So from the client class, let's create an instance of the send goal options class.
And now we can use this send goal option class to define a series of callback functions that will be
executed when some event occurs during the interaction between the client and the server.
So let's use this send goal option for example, to add a goal response callback.
So with the goal response callback and with this we can define the function so the behavior that the
client will execute after sending the goal to the action server.
So basically when the action server responds to the action client saying whether or not it accepted
the goal.
So let's use again the bind function of C plus plus, and let's say that we are going to execute a function
called goal callback and that we are going to define this function within the simple action client.
So let's say that here we are going to define this function within the simple action client class and
also that we want to use the current object, the current implementation.
And then also this function will receive one parameter as input.
So here let's use.
The namespace placeholders.
And from this namespace let's use the placeholder one to indicate that the goal callback function will
receive one variable.
So one parameter as input.
Now let's still use the send goal option class.
So the send goal option instance that we have just created.
And let set also a feedback callback function.
So let's set the feedback callback function that is the function that will be executed every time the
action server sends a feedback message to the client to inform it about the progress of the action.
Again, let's use the bind function to set the name of the function and we are going to call this one
feedback callback.
Again, we are going to define this one within the simple action client class.
Let's pass the pointer This and the feedback callback function will receive two inputs.
So two variables as input.
And so let's use the placeholders one and two to indicate that it's going to receive two inputs.
Finally, let's use one more time the send goal option object that we have created in order to set a
result callback function.
So the function that will be executed when the action server terminates its execution and returns a
result message to the client.
And also let's use the bind function also in this case and we are going to execute the result callback
function still defined within the simple action client class and we want to use the current implementation
of this function and it is going to receive just one parameter as input.
At this point.
We have here the goal message that contains the actual goal that we want to send to the action server.
And so, for example, the order of the Fibonacci sequence that we are going to execute is ten.
And then also we have defined several callback functions, one for each event that is generated by the
action server.
So this one, the goal callback function, then the feedback callback function and the result callback
function.
Now we can finally send the goal to the action server.
So let's use the client variable and then let's use the function async send goal.
So let's use this function here and it receives as input the goal message.
So the actual goal that contains the order of the Fibonacci sequence and also the goal options.
So the variable that we called send goal options, which contains all the callback functions that are
going to be executed.
Now let's proceed to define each of these callback functions, starting from the goal callback function
which is executed when the client has finished sending the goal message to the server and the server
has accepted or rejected the goal.
So here, still within the private attributes of the simple action client class, let's define the goal
callback function.
And it receives as input an object.
So from the CPP action library, it is an object of type client goal and this is a template class.
And also we receive a shared pointer to this object.
And let's call this one goal Endl.
As the client goal and class is a template class we need to provide to this class the type of the interface
that is used in order to communicate with the action server.
And so the interface is this one.
So the Fibonacci interface defined in the Arduino board messages within this function.
So within the goal callback function, the first thing that we are going to do is to check whether or
not the goal has been accepted by the action server.
So let's check if not goal and all.
So this means that the goal has not been accepted in this case.
Let's print an error message.
So let's print an error message.
Let's use the Http get logger function to get the Xcp logger and let's print the message.
Goal was rejected by the server.
And otherwise.
So else this means that the goal has been accepted and so we can print an informative message in the
terminal.
So let's still get the longer.
And the message says go.
Accepted by the server.
Waiting for a result.
And with this, our goal callback function is completed.
So basically it will just print two messages in the terminal whether or not the goal has been accepted
by the server or rejected.
Next, let's move on to define the feedback callback function.
So still here among the private attributes of the simple action client class, let's define the feedback
callback function.
And it receives also a shared pointer to a client goal handle object.
So the input is the same as this one.
So let's copy this one and let's paste it.
And then it also receives the actual feedback message of the Fibonacci interface.
So it takes two parameters as input and the second one is another shared pointer.
This time to a const object of the class Arduino board messages.
So from the interface Fibonacci.
The feedback message.
And let's call this one feedback.
Let's format it a little bit better so that we can easily see it.
Actually in the feedback callback function, this doesn't need to be const.
And also we can remove the name of the variable since we are not going to use the goal endl object,
but you are just going to print in the terminal the feedback message that was just received.
So in this function here, upon receiving the new feedback message, let's simply print the partial
sequence into the terminal.
So let's create a new object of type string stream, and let's call this one string stream.
And then let's add to this stream, the string next number in sequence received.
And so let's print the number.
So here now for each of the elements that appear in the partial Fibonacci sequence, let's add them
to the string stream.
So for each number that appear in the feedback message and in the partial sequence, let's simply add
it to the string stream.
So to the SS object, let's append the number and then let's also append a space in order to improve
its readability.
And then after this for loop, let's simply print a new message into the terminal.
And so in this message, let's just print the SS object.
Let's convert this one into a string and then into a C type string so that we can print it with the
CP info function.
Now we are only missing to define the result callback function.
So still let's define this one.
And this function receives as input an object of the wrapped result class.
This class is defined within the CPP action.
Then within the definition of the client goal end class, which is a template class and which takes
as input the type of the interface.
So the Fibonacci interface.
And then from this one, let's take the rapid result class and let's call this variable result in order
to access to the result of the action server.
So this variable contains some codes, so different codes that represents the result of the action execution.
And so we can use a switch statement in order to check which was the output of the action server.
So let's use a switch statement on the result variable and on the code that it returns in order to check
the status.
So if the action server completed successfully, then the status.
So the result code is of type CPP action, then result code succeeded.
So this is the status when the action server execution is completed correctly.
In this case, we don't print any message into the terminal and let's simply break the switch statement.
Otherwise.
So case, if it's concluded in an aborted state, the code that is returned by the result variable is
of type lclc-rp action.
And still from the result code.
This is of type aborted.
And in this case, let's print an error message in the terminal.
So.
Let's use the TCP get logger function.
To print the message goal was aborted.
And also in this case, let's terminate the execution of the function.
So with the return otherwise.
So a new case if the action server completed with a cancel code.
So it returns a code of type Lclc-rp action result code cancelled.
And so in this case, let's print a message in the terminal.
So let's copy this one.
And the message says goal was cancelled.
And also in this case, let's return.
So let's terminate the execution of this function.
Finally, if none of this code is returned.
So if the code of the action server doesn't fall into any of the previous categories, let's print another
error message in the terminal that says so here.
Unknown result code.
And also in this case, let's return.
So let's terminate the execution of the function after the switch statement.
So after this statement here, if this script so the result callback function is still executing, it
means that basically the result code of the action server was succeeded.
So we felt in this case because otherwise we would have returned the function.
So the function will not still be executing.
So in this function let's just print the full Fibonacci sequence that we received in the result message.
So it means that the action completed successfully and so we can print the full sequence.
So the full Fibonacci sequence into the terminal, we can copy exactly what we did for the feedback
message.
So let's copy this one and let's paste it here.
And the only thing that we need to change is that here we don't have the feedback message, but we have
the result message.
And from this one, let's take the result.
And then from the result interface the object.
So the variable that contains the full sequence is called simply sequence.
Finally.
So here within this function, since with this, the client has successfully sent the goal to the action
server and also has successfully received the result.
We can shut down already this node with rcl cp-rp shut down function.
With this, the simple action client node is completed and now we can define the main function where
actually its functionalities are used.
To start a ros2 node that implements an action client.
We can follow the same approach that we used for the action server and namely we can import a new library.
So let's include the library Rql CPP
components.
And from this library let's include the register node macro and then from this library at the end of
this script and also take care of inserting it at the end of the namespace.
Let's use the macro Rql CPP components register node.
And now let's pass the name of the class which implements the action client.
So this is in the Arduino CPP examples and is called the Simple Action Client.
Let's save this note.
And now we can instruct the compiler on how it should build and make this a C plus plus script and executable.
So here in the cmakelists.txt for installing the node that implements the action client, we can copy
paste the same instructions that we used also for the action server.
So all of these instructions and let's paste it below.
And now we need to do some small changes, like for example the name of the executable.
That is a simple action client.
Let's copy this one.
Also, the name of this script is the Simple Action Client Dot CP here.
Let's change the name of the executable.
Let's also change it here, the name of the executable and also here in the C plus plus building DLL.
This is the simple action client.
Also here, let's change the name of the executable, the dependencies of this script.
So this simple action client node are the same.
Let's also change the name of the executable here and let's change it here.
So we are going to call this one simple action client node and the plugin is defined still in the Arduino
CP examples namespace, but this time the class is the simple action client.
Finally, let's also copy the name of this library.
So the simple action client and let's list it where we list it.
So in the install instruction where we already inserted and we already installed this simple action
server.
So let's save also this file.
And since we haven't added any new dependencies, we don't need to modify the package dot XML file so
we can directly proceed to build our workspace and run our node.
So in a new terminal, let's go to the workspace and let's build it.
We can see that the build failed.
So most probably we did something wrong.
So normally, in order to correctly debug some cplusplus node, let's go at the beginning of the error.
And so we can see that there is an error in the declaration of the feedback callback function.
So let's go back to Visual Studio Code.
The simple action client.
And actually this function doesn't need a reference, so let's save this one.
Now also, we can see that Visual Studio code is correctly recognizing this function and we can build
our workspace again.
And also there is another error.
So this time it is related to the node options which the required form.
So the required node option format is to be const.
So back in Visual Studio code, this one here needs to be const.
Let's try to build it one more time.
So Qualcomm build.
And now everything is fine so we can move on to start first our action server and then the action client
that we created in this lesson.
So let's start by opening a new terminal here.
Let's source the workspace.
So the file setup dot bash and from the Arduino bot.
CP examples.
Let's start the simple Action server node.
So let's press enter.
And now that the action server is running, we can also start the client.
So in a new terminal, let's still source the setup dot batch file.
And here let's run.
So with the command Ros to run from the Arduino but Pi examples package.
Let's start the simple action client node.
Okay, this one.
So let's press enter and as soon as we start the client, we see that the message that appears is call
accepted.
And also it means that the action server has accepted our goal and also we can see that it begins to
send.
And so to print also feedback messages with the partial Fibonacci sequence calculated up to this moment.
Then also we can see that at the end of the execution of the action.
So when the sequence has reached the desired order that we specified in the goal message, the server
also returns the result with the complete sequence.
And so then the client terminates its execution.
Now let's try a different thing.
So, for example, let's try to interrupt the action server and let's try.
So if we use the command Ros2 action list, actually we can see that there are no action servers available
in Ros2.
And now if we start the client again, we can see that basically nothing happens.
So this node remains idle and this is waiting for the action server to be started.
And this is thanks to the function wait for action server.
So this is thanks to this statement here.
In fact, we can see that as soon as we start the action server again, then the action client is able
to send the goal.
So the goal is accepted and also it starts receiving feedback messages and then at the end it also receives
the result message.