Now that we know how to create and use an action server and also how to interact with it through the
Ros2 command line interface, let's see how we can create a Python action client node that use the Fibonacci
action server that we created in the last laboratory lesson.
If you are only interested in the C plus plus development, then you can skip directly to the next lesson
in which we are going to implement the same node.
So the same action client in C plus plus.
Just as we did with the server node.
Let's also place the client node inside the Arduino board Pi examples and so inside the Arduino board
by example sub folder.
And here let's create a new file called Simple Action Client Dot Pi.
Here.
Let's start by importing the Pi library and also from the Pi library and from the node modules.
Let's import the node class.
And then in order to create a Ros two node, let's create a new class called Simple Action Client that
inherits from the node class that is defined in the node module.
So the simple action client inherits from the node class.
And now let's define the constructor.
So this is a member of the Simple Action Client class.
And within the constructor, let's start by initializing the object of the node class.
So of the base class.
So let's call the init function on the object of the base class and let's pass a string as input.
That is the name that we want to give to the node.
So let's call it simple action client.
Next, let's initialize and create the actual action client.
And so in order to do this, let's import another library.
So still from the Pi library, this time from the action module, let's import the definition of the
action client class, and now let's use it to create a new object.
So here, still within the constructor, let's create a new object and let's call this one action client.
And this is an instance of the action client class.
The constructor of this class takes the instance of the Ros2 node where the action client is implemented,
which is the current simple action client class.
So we can refer to this one with the self variable.
It also takes as input the type of the interface that we want to use for the communication with the
action server.
And we define this interface in the Arduino bot messages package.
So from this package, from the Arduino bot messages and from the action module, let's import the Fibonacci
interface.
And now let's use this interface, the Fibonacci for the communication with the action server.
So basically we are saying that we want to create an action client which communicates with a certain
action server using the Fibonacci interface.
And also finally, let's specify the name of the action server that is the Fibonacci.
At this point, we could already use the action client to send the goals to the server.
However, before doing that, let's first verify that the action server is actually running using the
function.
So on the action client object, let's use the function, wait for server.
And so this function will block the execution of the client.
So of this script until an action server that is called Fibonacci and that uses the Fibonacci interface
is available.
So it becomes available in Ros2.
So after this function, if we insert any instruction after this function, we are sure that an action
server exists.
And so it means that we can send a goal to this action server.
So requesting its execution.
So let's start by creating a new goal variable.
Let's call this one goal and this is an instance of the Fibonacci interface and of the goal message.
And now let's, for example, request the calculation of the Fibonacci sequence up to the order ten.
So let's access to the goal message and then to the order variable.
And let's set this 1 to 10.
And now in order to send the goal to the action server, we can use the function.
So still using the action client, let's use the function, send goal async and this receives as input
the goal that we want to send to the server.
And so basically the variable goal that we have just created.
And also here in this end goal async function, we need also to pass the name of the function that we
want to execute when a new feedback message is received by the client.
So every time the action server will send a feedback message, the client will execute a certain function
in order to inform about the progress of the action.
And for example, let's call this function.
So the feedback callback function, let's call this one again feedback callback.
And we are going to define this feedback callback function still within the simple action client node.
Now the send goal async function.
This one will send a goal to the action server and returns a future variable.
And so let's store this one within a variable called future that takes the output of the send goal async.
This variable.
So the future variable provides a mechanism to access the result of an asynchronous operation.
And so, for example, we can also add a callback to this variable in order to be notified when the
action server completes its execution.
So on the future variable.
Let's add a new callback function by using the add done callback function.
And so when the server is done, so basically when the action client gets notified that the server completed
its execution, we want to execute the response callback function.
And so now we need to define this function.
So we need to define the response callback function and let's define it as a member still of the simple
action client node.
And so it receives the self variable.
And also this function receives as input the future variable.
So the future mechanism that allows to access to the result of the action server once it is done.
So first of all, let's save the result of the future variable into a new variable that we can call
goal handle.
And so let's take the future and let's use the function result in order to get the result of this server.
Now we can check if the goal that we sent has been accepted or not by the action server.
So let's check if not the goal handle variable.
So the goal handle is accepted.
So if this is not accepted, let's print an informative message.
So with the get logger function, let's print an informative message that says goal rejected.
And in this case, let's print a sad face and then let's also return.
So let's terminate the execution of the action client with the return statement otherwise.
So if we go here, if the goal was accepted, let's print again a new log message.
So an informative message that says goal accepted.
And in this case, let's print an face.
Then, since the goal was accepted and the action server has started its execution, we can use the
goal and all variable.
So this one here to get the result of the action server execution.
And so let's use this function.
So the goal endl and the function get result async.
So the response callback function, this one will be executed when we receive a response from the action
server.
And this basically informs us just if the server has accepted or not, our goal and now with this other
function.
So with the get result async, we are basically at the point in which we are sure we know that the action
server accepted our goal and now we want the result.
So we want the result of the execution of the logic of the action server.
So as this is also an asynchronous function, as you can say from the name, it also returns a future
variable.
So let's store this one in the variable future.
And now, as we did previously, let's also add a callback function to this future variable.
In order to be notified whenever the server completes the execution of the goal.
So still on the future variable, let's call the function add then callback and this receives the name
of the function.
So of the callback function that we want to execute when the server finishes its execution.
In this case, let's execute the function result callback.
So here is self result callback.
And so still we are going to define this function as a member of the simple action client node.
So let's define this function and it receives the self.
And also this function receives the future variable that contains the mechanism to access the response.
And so the result of the action server.
So let's start with this one with taking the result of the action server and let's store it in a new
variable called result.
And so this takes the future variable and then the result function.
And from this one, let's take the result.
So here we are accessing to the result of the future variable.
And here we are accessing to the result of the message interface of with the action server.
Now this variable here.
So the result variable contains the result of the action server.
So the full Fibonacci sequence.
Now let's print this sequence within the terminal.
So with the get logger function, let's print an informative message that says result and then let's
print the full vector.
So let's format this string and let's print basically the result sequence.
So let's print the full Fibonacci sequence that was calculated by the action server.
Once we have received correctly the result from the action server.
Now our simple example node.
So the action client has finished basically its execution, so it doesn't need to execute any more actions
and so we can actually shut down completely the node with the Pi shutdown function.
Now, as you can see, we are only missing to declare one function.
So to define the feedback callback function.
Let's also declare this one as a member.
So here of the simple action client.
And so let's pass the self in order to indicate that this belongs to the simple action client class.
And also this function receives as input the feedback message that was sent by the action server.
So let's call this variable feedback message.
Now when this happens.
So when this function is executed, it means that we received a new feedback message from the action
server.
And so let's simply print in the terminal its content.
So let's use the get logger function and then let's print an informative message that says received
feedback and then followed by the content of the feedback message.
So let's format this string and let's print from the feedback message.
So let's access to the feedback variable.
And here let's print the.
Partial sequence.
With this, the simple action client class is completed and now, since we have defined several callback
function, let's quickly recap the purpose of each of them.
The response callback function.
So this one is executed after we send the goal to the action server and basically it checks whether
or not the action server accepted our goal.
Then the result callback function is executed at the end of the action server execution.
So after the server has accepted and completed the action.
And finally this one, this last one, the feedback callback function is executed repeatedly.
So each time we receive a new feedback message from the action server during its execution.
Now we only need to declare also the main function.
And so here, let's call the main function and let's define this function here above and here.
Let's start by initializing the ros2 node.
So rql pi init in order to initialize the node.
Then let's create a new instance of the simple action client class and let's call this one action client
as an instance of the simple action client.
And then also let's keep this node up and running with the Pi dot spin function.
And then with this we have done with our script.
Now, as usual, we also need to add this script to the setup dot pi file.
So to this one in which we have already installed, for example, the simple action server.
So we can copy this instruction here also for the installation of the simple action client node that
is still in the Arduino Pi examples folder and is in the simple action client script.
And from this one we want still to execute the main function.
Now, let's also save this file.
And as we haven't added any new dependencies to our package, we don't need to modify the package dot
XML and so we can directly proceed to build and run our node.
To do so, let's open a new terminal.
Let's go to the workspace and let's build it.
Once the build is done, let's open a new window of the terminal.
And here, let's start by launching the Simple Action Server.
So let's start the action server by sourcing the workspace setup bash.
And then we click on Ros to run from the Arduino Pi examples.
Let's start the simple Action server.
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 source the workspace.
So if I set up Bash and here in order to launch the client node, let's use the command Ros to run and
steal from the Arduino board.
Pi Examples package.
Let's start the simple action client node.
So let's press enter.
Here, we can see that there is an error as we need here in this function, to pass the name of the
node that we want to spin.
So here there was a small error, so let's save this one.
But anyway, we can see that we were able to send the goal to the server.
Now let's run this again.
So let's build it and let's see again how it works.
So now the node should remain in execution, so the actual client should remain in execution and also
receive the feedback messages and the result messages from the server.
Now let's start the client again and now correctly.
As soon as we start the client, we see the message Goal accepted.
So it means that we correctly send the goal and it was accepted by the server.
And also we can see that we are starting to receive all the feedback messages that are sent by the action
server and also at the end of the action.
So here we can see that the last message that the action client receives before terminating its execution
is the full Fibonacci sequence.
So it's the result message.
And here this is the result of the execution of the action server.
Now if, for example, we terminate the execution of the action server.
So now we can see that there is no action server with the command Ros2 action list.
Okay.
Now actually there aren't any action servers available in Ros2.
And if, for example, we restart the action client, we can see that nothing happens.
And so basically that this node is doing nothing, is idle and is waiting for an action server named
Fibonacci to become available.
And so this happened because here we use the function wait for server.
So basically now our script is blocked, is stuck at this instruction until a Fibonacci action server
that uses the Fibonacci interface becomes available.
And so as soon as we restart the action server, actually the client is able to send the goal and also
then to receive feedback messages and then at the end also to receive the result from the action server.