In this laboratory lesson, we will create a simple Ros2 lifecycle node in C plus plus that subscribes
to a topic and displays in the terminal the current state of the lifecycle node, and also any new message
that is received from the topic only if the lifecycle node is in the active state.
Since we are creating a simple node.
So a template script that we are not going to use to add new functionalities to the robot, but only
as a template for you to understand how life cycle node works, and also in order to use it in your
project, let's create this new node within the Arduino bot CP examples.
And so within the source folder.
And let's call this one simple lifecycle node dot cp.
cpp.
Let's start by importing.
So let's include the rcl cpp library this one.
And then from this library we can use the Ros2 functionalities such as for example the subscriber class
to subscribe to a certain topic instead.
To create a Ros2 lifecycle node we also need to include another library.
And so let's include the RCL, cpp Life Cycle library.
And from this one let's import the life cycle node.
Now whether to create a simple ros2 node, we had to create a class that inherited from the node class
defined in the cl cpp library.
To create a life cycle node, we also need to create a class.
So, for example, let's call this one simple life cycle node.
But this time we need to inherit from the cl cpp lifecycle namespace and from the life cycle node.
So this time we have to inherit from this class.
Let's define this class and let's start by defining its constructor.
So among the public attributes let's define the explicit constructor of the simple life cycle node class.
And this constructor takes as input a string.
That is the name of the node.
So let's call this one node name.
And also it takes as input a boolean variable that is called inter process communication.
So comms that by default we set to false.
So if no one is assigning a value to this parameter here we are setting it to false.
When we create an object of the simple life cycle node class.
So here when we create the constructor, let's also call the constructor of the base class.
So the constructor of the life cycle node class from which we are inheriting.
So let's call the life cycle node class constructor.
And this one receives the name of the node.
So the variable that we called node name and also it receives a new object of type clcp.
Node options that we set to a new empty one, and also to this one.
Let's set the use inter-process communication equal to the inter process communication variable that
our constructor.
So the constructor of the simple lifecycle node takes as input.
Let's format this a little bit better so that it's more readable.
At this point we have created and initialized our lifecycle node, and as seen in the theoretical lesson,
when the lifecycle node is created, it is in the Unconfigured state, and in this state there are only
two transitions available the configured one and the shutdown one.
Let's define within the simple lifecycle node class the behavior of these two transitions.
So of these two functions that respectively bring the state machine so the lifecycle node to the inactive
or to the finalized state.
Let's start by defining the Unconfigure function so we can define it here.
And this function returns.
So here it returns an object of the Clcp lifecycle namespace of type node interfaces of type lifecycle
node interface and of type callback return.
And instead it takes as input.
So let's go here it takes as input a const object of type clcp lifecycle.
And it is a state.
And this is a reference to this object when this transition is activated.
So within the body of this function we want the life cycle node to register a new subscriber to a Ros
two topic.
So let's first add this member among the private variable of the class.
So let's define among the private variable a new subscriber.
So from the Clcp library let's create a new subscription object as we already know how to do it.
And let's take a shared pointer to this object and let's call this variable soup.
Let's also remember to include the the memory module of the C plus plus standard library.
And now here so within the angular brackets of the subscription we need to define the type of the interface
that this subscriber will use to receive messages from the topic.
And in this topic.
So in the topic to which we want to register there are just transmitted string messages.
So let's define that from the standard messages library.
From the messages we want just the string message, and we want to use this one for our subscriber.
So from the standard messages we want the string message in the Unconfigure function.
So here in the body of this function let's initialize this subscriber.
So this one here.
And as we have been doing throughout the course to initialize our subscriber we can use the function
create subscription.
And this is a template function which receives as input the type of the interface, which again is a
string from the standard message library and within the parentheses.
Instead it receives the name of the topic.
For example, let's use the topic chapter.
Then it receives the queue size, and then it also receives the name of the callback function that needs
to be executed whenever a new message is received within this topic.
Again, let's use the bind function from the standard library.
And then let's say that the function that we want to execute is the message callback function.
And also let's say that we are going to define this function here within the simple life cycle node
class.
Then let's also use the pointer this to say that we want to use the current implementation of the simple
life cycle node class.
And then also let's use the namespace the placeholder.
And from this one let's take just the placeholder number one to indicate that this message callback
will receive only one input, so only one parameter as input.
That is exactly the message that was transmitted on the chatter topic once the subscriber is initialized.
So here let's print a message in the terminal.
So let's use the get logger function and let's print the message lifecycle node.
On configure called.
And so this way we exactly know where the lifecycle node is.
So on which state of the state machine the lifecycle node is currently.
And then as this function.
So as the Unconfigure function returns an object of type callback return.
Let's return an object of this type and so we can copy all of this actually.
And the type of the return is success.
So we return a callback return of type success to indicate that the transition to the inactive state
is completed, and so this is success.
Now let's define the second function that can be executed when we are in the unconfigured state, which
is the shutdown transition.
So we can actually copy the Unconfigured function.
So you can actually copy this one and let's paste it.
And we just need to change the name of this function to be on shut down.
And also for this function, the return type and the input type of this function are the same.
So when executed this function.
So this transition moves the state machine of the life cycle node into the finalized state.
And this state precedes the destruction of the node.
So before destroying the node let's first deactivate the subscriber.
So let's take the shared pointer that we called soap and let's reset it.
So basically with this we are deactivating our shared pointer.
And so our subscriber then let's change the message that we are printing in the console.
So lifecycle node here the on shut down function is the one that is executed and also return the same
message.
So let's still return a callback.
Return success to indicate that the transition was executed correctly.
And so now the state machine is in the state finalized.
Once we have configured the lifecycle node with the Unconfigured transition, it enters in the inactive
state.
In this state we have three transition available.
The clean up which brings the state machine back to the unconfigured state.
Then the shut down which we have already implemented and which brings the state machine to the finalized
state.
And then the activate function, which brings the state machine into the active state.
Let's start by defining the on clean up transition which brings the node back to the unconfigured state.
So let's copy the on shut down function and let's paste it below.
And let's change the name to be on clean up.
So the input is the same.
And also the output it returns is the same.
And let's change just in this function here the message that we are going to print in the console,
it's the on clean up function called.
So now we get the feedback that this function has been called.
As with this function, we are going to return to the Unconfigured state in which we are going to create
the subscriber.
Before going to the unconfigured state, we need also to reset.
So so we need to deactivate the subscriber.
Now let's also define the on activate function.
So once more we can copy this function here.
And let's change the name of this function to be on activate function.
Also in this case the return type is the same and the input type is the same.
And when this function is executed, let's call the on activate function on the base class.
So on the life cycle node class let's execute the on activate function.
And to this function we need to pass the current state of the state machine that we can take from the
input of this function.
So of the on activate function.
And let's give it a name to this variable state.
And so we pass this one also to the on activate function of the base class.
Then we are not going to need this one.
And the message that we are going to print in the terminal is life cycle node on activate cold.
Then in this function let's simulate the execution of various operations.
So let's simulate that the node is actually doing something.
And to do so let's just pause this thread for a few seconds.
So let's include.
The thread modules.
And now let's use also the namespace.
Chrono literals.
And from this one we can here execute this function.
So this thread and let's make it sleep for and for example, let's make it sleep for two seconds.
As always, the return message indicates that the transition has been successfully executed.
And so the state machine now is successfully in the active state.
Once in this state.
So once the state machine is in the active state, there are only two transitions available.
The shutdown, which we have already defined and brings the state machine to the finalized state, and
the deactivate transition, which brings the lifecycle node back to the inactive state.
So let's define this deactivate transition this deactivate function so we can copy the on activate function.
And let's change the name to on the activate function.
And within this function.
So when this is executed let's call the on deactivate function of the base class.
So of the lifecycle node class.
Let's change also here, let's inform the user that we are calling the on the activate function.
And this is actually all we need.
So we can remove the sleep.
So we can immediately return a success to indicate that the state machine is back in the the active
state.
Now we have just one thing left to define that is the message callback function.
So in fact here we still have an error.
So let's define this function.
Let's define it here.
And this is the one that is executed whenever a new message is transmitted within the chapter topic.
So is received by this node.
So the return type is void and the input type it receives the message that was transmitted in the topic.
And so this is a standard message.
Of type string.
And let's call this variable here message.
When we receive a new message on the chatter topic.
If the life cycle node is in the active state, we want to display it into the terminal.
Otherwise we just ignore it.
So let's first check which is the current state of the state machine.
And let's use the function get current state.
So we have basically inherited this function from the life cycle node to which we are inheriting.
And now the state variable.
So this one here that we have just created is an object of the state class from the Clcp lifecycle.
So basically this object here is an object of this type.
So of this type here.
And so let's use this variable.
So this state to check whether the state machine is currently in the active state.
So if the state then let's call the label function.
This is equal to active.
So this means that if the current state of the state machine is equal to the string active, so only
when the state machine is in the active state, we want to print the messages that we are receiving
within the chatter topic into the terminal.
So let's use the rcl cpp info stream function And here let's use the Get Logger function to print the
message lifecycle node Earth, and then followed by the message that was just received.
And so the message dot data.
And convert this to a C type string.
Let's save it.
And with this our simple life cycle node class is completed.
So we have defined all the functions that defines all the transition of the state machine.
Now we can move on to define the main function.
And within this function, as always, let's initialize the communication with Ros2.
Then let's create a new object of type single thread executor.
And so from the Clcp from the executors.
Let's create a single thread executor object and let's call this 1STE here.
This one is not needed.
And now let's initialize a new shared pointer to an object of this class.
So to this simple lifecycle node class.
So let's call this variable simple lifecycle node.
And let's use the function make shared to create a new shared pointer to the simple lifecycle node class.
And let's assign it a name.
So as you might remember, the constructor of this class requires a string as input.
So the name of the node that we can call simple life cycle node.
Then let's use the state object.
So the single thread executor object to add a node to the single thread executor.
And so let's add the simple life cycle node.
And from this one we want to execute the node.
So let's take the get base node interface.
And now let's just keep the single thread executor up and running.
So the function spin and when it finishes.
So when we interrupt its execution with control C we want to shut down the communication with Ros.
So let's call the shutdown function.
Finally for this lesson we need to declare the dependencies and to install this new node in the Arduino
bot CP examples package.
So let's open the file Cmakelists.txt.
And here let's add a new dependency.
So with defined package, let's add a dependency from the Clcp life cycle package.
And then let's add a new executable.
So actually we can copy this one and we can paste it.
And let's change the name of the executable into simple lifecycle node.
And then the script that implements.
So the source code is this one that is in the source folder.
And this is called simple lifecycle node dot cpp.
Then let's add the dependencies so to the simple lifecycle cycle node which uses the Clcp library.
Then the Clcp life cycle and the standard messages.
And then let's copy the name of this executable and let's add it into the install.
So here into the install statement in which we have already installed all the previous nodes that belongs
to this package.
Finally, since we added few more dependencies into the CMake list, let's also declare them within
the package dot XML.
So here let's add a new dependency from the rcl cpp lifecycle package.
And these are all the dependencies we need to conclude this lesson.
Let's build our workspace to verify that there are no errors and that actually the compiler is able
to generate an executable for our simple lifecycle node.
So let's go to the workspace and let's build it with the command called build.
The build is successful.
And so in the next lesson we are going to see how to use the lifecycle nodes.