WEBVTT

00:00.530 --> 00:08.600
In this laboratory lesson, we will create a simple Ros2 lifecycle node in C plus plus that subscribes

00:08.600 --> 00:15.650
to a topic and displays in the terminal the current state of the lifecycle node, and also any new message

00:15.650 --> 00:21.800
that is received from the topic only if the lifecycle node is in the active state.

00:22.220 --> 00:24.080
Since we are creating a simple node.

00:24.080 --> 00:29.910
So a template script that we are not going to use to add new functionalities to the robot, but only

00:29.910 --> 00:35.580
as a template for you to understand how life cycle node works, and also in order to use it in your

00:35.580 --> 00:41.400
project, let's create this new node within the Arduino bot CP examples.

00:41.400 --> 00:43.590
And so within the source folder.

00:43.950 --> 00:52.500
And let's call this one simple lifecycle node dot cp.

00:52.500 --> 00:52.520
cpp.

00:53.570 --> 00:55.700
Let's start by importing.

00:55.700 --> 01:02.420
So let's include the rcl cpp library this one.

01:02.420 --> 01:08.510
And then from this library we can use the Ros2 functionalities such as for example the subscriber class

01:08.510 --> 01:11.570
to subscribe to a certain topic instead.

01:11.570 --> 01:15.710
To create a Ros2 lifecycle node we also need to include another library.

01:16.130 --> 01:23.580
And so let's include the RCL, cpp Life Cycle library.

01:23.760 --> 01:28.800
And from this one let's import the life cycle node.

01:28.950 --> 01:36.030
Now whether to create a simple ros2 node, we had to create a class that inherited from the node class

01:36.030 --> 01:38.310
defined in the cl cpp library.

01:38.310 --> 01:42.260
To create a life cycle node, we also need to create a class.

01:42.800 --> 01:49.850
So, for example, let's call this one simple life cycle node.

01:49.910 --> 02:03.050
But this time we need to inherit from the cl cpp lifecycle namespace and from the life cycle node.

02:03.050 --> 02:05.790
So this time we have to inherit from this class.

02:06.150 --> 02:10.530
Let's define this class and let's start by defining its constructor.

02:10.530 --> 02:20.580
So among the public attributes let's define the explicit constructor of the simple life cycle node class.

02:21.150 --> 02:23.970
And this constructor takes as input a string.

02:26.400 --> 02:28.230
That is the name of the node.

02:28.460 --> 02:32.720
So let's call this one node name.

02:32.720 --> 02:43.670
And also it takes as input a boolean variable that is called inter process communication.

02:43.670 --> 02:48.260
So comms that by default we set to false.

02:48.260 --> 02:54.540
So if no one is assigning a value to this parameter here we are setting it to false.

02:54.870 --> 02:58.080
When we create an object of the simple life cycle node class.

02:58.080 --> 03:04.470
So here when we create the constructor, let's also call the constructor of the base class.

03:04.470 --> 03:09.990
So the constructor of the life cycle node class from which we are inheriting.

03:09.990 --> 03:14.700
So let's call the life cycle node class constructor.

03:14.820 --> 03:17.480
And this one receives the name of the node.

03:17.480 --> 03:26.300
So the variable that we called node name and also it receives a new object of type clcp.

03:27.200 --> 03:32.960
Node options that we set to a new empty one, and also to this one.

03:32.960 --> 03:43.530
Let's set the use inter-process communication equal to the inter process communication variable that

03:43.530 --> 03:44.730
our constructor.

03:44.730 --> 03:48.060
So the constructor of the simple lifecycle node takes as input.

03:48.450 --> 03:53.910
Let's format this a little bit better so that it's more readable.

03:53.910 --> 04:01.080
At this point we have created and initialized our lifecycle node, and as seen in the theoretical lesson,

04:01.080 --> 04:07.970
when the lifecycle node is created, it is in the Unconfigured state, and in this state there are only

04:07.970 --> 04:13.340
two transitions available the configured one and the shutdown one.

04:13.910 --> 04:19.190
Let's define within the simple lifecycle node class the behavior of these two transitions.

04:19.190 --> 04:26.030
So of these two functions that respectively bring the state machine so the lifecycle node to the inactive

04:26.030 --> 04:28.250
or to the finalized state.

04:28.730 --> 04:34.620
Let's start by defining the Unconfigure function so we can define it here.

04:39.060 --> 04:41.130
And this function returns.

04:41.130 --> 04:53.680
So here it returns an object of the Clcp lifecycle namespace of type node interfaces of type lifecycle

04:53.680 --> 04:59.080
node interface and of type callback return.

04:59.950 --> 05:02.020
And instead it takes as input.

05:02.020 --> 05:10.690
So let's go here it takes as input a const object of type clcp lifecycle.

05:11.350 --> 05:13.870
And it is a state.

05:15.190 --> 05:20.010
And this is a reference to this object when this transition is activated.

05:20.040 --> 05:27.060
So within the body of this function we want the life cycle node to register a new subscriber to a Ros

05:27.060 --> 05:27.930
two topic.

05:28.080 --> 05:32.820
So let's first add this member among the private variable of the class.

05:33.030 --> 05:37.800
So let's define among the private variable a new subscriber.

05:37.830 --> 05:47.080
So from the Clcp library let's create a new subscription object as we already know how to do it.

05:47.080 --> 05:53.770
And let's take a shared pointer to this object and let's call this variable soup.

05:53.980 --> 06:02.710
Let's also remember to include the the memory module of the C plus plus standard library.

06:02.920 --> 06:09.690
And now here so within the angular brackets of the subscription we need to define the type of the interface

06:09.690 --> 06:14.310
that this subscriber will use to receive messages from the topic.

06:14.370 --> 06:15.720
And in this topic.

06:15.720 --> 06:20.550
So in the topic to which we want to register there are just transmitted string messages.

06:20.610 --> 06:26.580
So let's define that from the standard messages library.

06:26.880 --> 06:33.010
From the messages we want just the string message, and we want to use this one for our subscriber.

06:33.250 --> 06:41.920
So from the standard messages we want the string message in the Unconfigure function.

06:41.920 --> 06:46.390
So here in the body of this function let's initialize this subscriber.

06:46.930 --> 06:48.460
So this one here.

06:48.460 --> 06:53.850
And as we have been doing throughout the course to initialize our subscriber we can use the function

06:53.850 --> 06:57.570
create subscription.

06:58.230 --> 07:05.250
And this is a template function which receives as input the type of the interface, which again is a

07:05.250 --> 07:09.510
string from the standard message library and within the parentheses.

07:09.510 --> 07:12.180
Instead it receives the name of the topic.

07:12.180 --> 07:15.420
For example, let's use the topic chapter.

07:15.420 --> 07:21.710
Then it receives the queue size, and then it also receives the name of the callback function that needs

07:21.710 --> 07:26.210
to be executed whenever a new message is received within this topic.

07:26.630 --> 07:32.000
Again, let's use the bind function from the standard library.

07:32.000 --> 07:38.540
And then let's say that the function that we want to execute is the message callback function.

07:38.540 --> 07:47.010
And also let's say that we are going to define this function here within the simple life cycle node

07:47.010 --> 07:47.700
class.

07:47.730 --> 07:54.090
Then let's also use the pointer this to say that we want to use the current implementation of the simple

07:54.090 --> 07:55.380
life cycle node class.

07:55.770 --> 08:02.520
And then also let's use the namespace the placeholder.

08:03.870 --> 08:09.730
And from this one let's take just the placeholder number one to indicate that this message callback

08:09.730 --> 08:14.500
will receive only one input, so only one parameter as input.

08:14.500 --> 08:21.880
That is exactly the message that was transmitted on the chatter topic once the subscriber is initialized.

08:21.880 --> 08:24.910
So here let's print a message in the terminal.

08:28.660 --> 08:37.530
So let's use the get logger function and let's print the message lifecycle node.

08:40.770 --> 08:42.840
On configure called.

08:42.990 --> 08:46.740
And so this way we exactly know where the lifecycle node is.

08:46.740 --> 08:51.240
So on which state of the state machine the lifecycle node is currently.

08:51.840 --> 08:54.220
And then as this function.

08:54.220 --> 08:59.620
So as the Unconfigure function returns an object of type callback return.

08:59.620 --> 09:06.310
Let's return an object of this type and so we can copy all of this actually.

09:08.740 --> 09:12.370
And the type of the return is success.

09:12.370 --> 09:19.160
So we return a callback return of type success to indicate that the transition to the inactive state

09:19.160 --> 09:22.130
is completed, and so this is success.

09:22.640 --> 09:29.090
Now let's define the second function that can be executed when we are in the unconfigured state, which

09:29.090 --> 09:31.280
is the shutdown transition.

09:31.280 --> 09:34.940
So we can actually copy the Unconfigured function.

09:35.660 --> 09:38.990
So you can actually copy this one and let's paste it.

09:38.990 --> 09:45.310
And we just need to change the name of this function to be on shut down.

09:45.490 --> 09:51.760
And also for this function, the return type and the input type of this function are the same.

09:52.150 --> 09:54.100
So when executed this function.

09:54.100 --> 10:00.130
So this transition moves the state machine of the life cycle node into the finalized state.

10:00.130 --> 10:03.370
And this state precedes the destruction of the node.

10:03.370 --> 10:07.970
So before destroying the node let's first deactivate the subscriber.

10:08.510 --> 10:14.240
So let's take the shared pointer that we called soap and let's reset it.

10:14.240 --> 10:17.750
So basically with this we are deactivating our shared pointer.

10:17.750 --> 10:22.640
And so our subscriber then let's change the message that we are printing in the console.

10:22.640 --> 10:34.000
So lifecycle node here the on shut down function is the one that is executed and also return the same

10:34.000 --> 10:34.600
message.

10:34.600 --> 10:36.670
So let's still return a callback.

10:36.670 --> 10:41.200
Return success to indicate that the transition was executed correctly.

10:41.200 --> 10:44.950
And so now the state machine is in the state finalized.

10:46.120 --> 10:52.900
Once we have configured the lifecycle node with the Unconfigured transition, it enters in the inactive

10:52.900 --> 10:53.530
state.

10:53.890 --> 10:56.960
In this state we have three transition available.

10:57.170 --> 11:01.910
The clean up which brings the state machine back to the unconfigured state.

11:01.910 --> 11:07.670
Then the shut down which we have already implemented and which brings the state machine to the finalized

11:07.670 --> 11:08.150
state.

11:08.150 --> 11:13.520
And then the activate function, which brings the state machine into the active state.

11:14.360 --> 11:21.190
Let's start by defining the on clean up transition which brings the node back to the unconfigured state.

11:21.940 --> 11:26.860
So let's copy the on shut down function and let's paste it below.

11:26.860 --> 11:31.690
And let's change the name to be on clean up.

11:32.380 --> 11:34.690
So the input is the same.

11:34.690 --> 11:37.390
And also the output it returns is the same.

11:37.390 --> 11:43.280
And let's change just in this function here the message that we are going to print in the console,

11:43.280 --> 11:48.650
it's the on clean up function called.

11:48.650 --> 11:52.970
So now we get the feedback that this function has been called.

11:52.970 --> 11:59.990
As with this function, we are going to return to the Unconfigured state in which we are going to create

11:59.990 --> 12:01.040
the subscriber.

12:01.040 --> 12:06.020
Before going to the unconfigured state, we need also to reset.

12:06.020 --> 12:09.010
So so we need to deactivate the subscriber.

12:09.880 --> 12:13.840
Now let's also define the on activate function.

12:14.140 --> 12:18.790
So once more we can copy this function here.

12:18.790 --> 12:23.890
And let's change the name of this function to be on activate function.

12:23.890 --> 12:29.380
Also in this case the return type is the same and the input type is the same.

12:29.380 --> 12:35.510
And when this function is executed, let's call the on activate function on the base class.

12:35.540 --> 12:42.140
So on the life cycle node class let's execute the on activate function.

12:42.140 --> 12:48.320
And to this function we need to pass the current state of the state machine that we can take from the

12:48.320 --> 12:49.580
input of this function.

12:49.580 --> 12:52.040
So of the on activate function.

12:52.580 --> 12:55.480
And let's give it a name to this variable state.

12:55.930 --> 13:01.300
And so we pass this one also to the on activate function of the base class.

13:01.600 --> 13:04.480
Then we are not going to need this one.

13:05.170 --> 13:13.120
And the message that we are going to print in the terminal is life cycle node on activate cold.

13:13.240 --> 13:18.220
Then in this function let's simulate the execution of various operations.

13:18.260 --> 13:22.070
So let's simulate that the node is actually doing something.

13:22.070 --> 13:26.120
And to do so let's just pause this thread for a few seconds.

13:26.660 --> 13:28.280
So let's include.

13:30.290 --> 13:32.270
The thread modules.

13:32.930 --> 13:38.210
And now let's use also the namespace.

13:40.460 --> 13:41.900
Chrono literals.

13:42.660 --> 13:47.580
And from this one we can here execute this function.

13:48.510 --> 13:59.610
So this thread and let's make it sleep for and for example, let's make it sleep for two seconds.

14:00.180 --> 14:05.820
As always, the return message indicates that the transition has been successfully executed.

14:05.820 --> 14:10.400
And so the state machine now is successfully in the active state.

14:11.150 --> 14:12.200
Once in this state.

14:12.200 --> 14:17.870
So once the state machine is in the active state, there are only two transitions available.

14:17.870 --> 14:23.870
The shutdown, which we have already defined and brings the state machine to the finalized state, and

14:23.870 --> 14:29.240
the deactivate transition, which brings the lifecycle node back to the inactive state.

14:29.630 --> 14:38.670
So let's define this deactivate transition this deactivate function so we can copy the on activate function.

14:38.760 --> 14:44.070
And let's change the name to on the activate function.

14:44.070 --> 14:45.480
And within this function.

14:45.480 --> 14:52.110
So when this is executed let's call the on deactivate function of the base class.

14:52.110 --> 14:54.240
So of the lifecycle node class.

14:54.240 --> 15:02.840
Let's change also here, let's inform the user that we are calling the on the activate function.

15:02.840 --> 15:04.820
And this is actually all we need.

15:04.820 --> 15:08.060
So we can remove the sleep.

15:08.060 --> 15:14.990
So we can immediately return a success to indicate that the state machine is back in the the active

15:14.990 --> 15:15.650
state.

15:15.650 --> 15:21.120
Now we have just one thing left to define that is the message callback function.

15:21.120 --> 15:24.060
So in fact here we still have an error.

15:24.060 --> 15:26.040
So let's define this function.

15:27.090 --> 15:29.160
Let's define it here.

15:30.060 --> 15:36.150
And this is the one that is executed whenever a new message is transmitted within the chapter topic.

15:36.150 --> 15:37.950
So is received by this node.

15:38.580 --> 15:46.010
So the return type is void and the input type it receives the message that was transmitted in the topic.

15:46.310 --> 15:49.550
And so this is a standard message.

15:52.130 --> 15:54.680
Of type string.

15:54.770 --> 15:57.830
And let's call this variable here message.

15:58.670 --> 16:01.790
When we receive a new message on the chatter topic.

16:01.790 --> 16:07.740
If the life cycle node is in the active state, we want to display it into the terminal.

16:07.770 --> 16:09.810
Otherwise we just ignore it.

16:10.410 --> 16:14.250
So let's first check which is the current state of the state machine.

16:14.250 --> 16:19.200
And let's use the function get current state.

16:19.200 --> 16:27.150
So we have basically inherited this function from the life cycle node to which we are inheriting.

16:27.810 --> 16:29.760
And now the state variable.

16:29.760 --> 16:36.230
So this one here that we have just created is an object of the state class from the Clcp lifecycle.

16:36.230 --> 16:40.880
So basically this object here is an object of this type.

16:40.880 --> 16:42.650
So of this type here.

16:42.650 --> 16:44.180
And so let's use this variable.

16:44.180 --> 16:49.520
So this state to check whether the state machine is currently in the active state.

16:49.700 --> 16:55.110
So if the state then let's call the label function.

16:55.110 --> 16:56.850
This is equal to active.

16:57.810 --> 17:04.950
So this means that if the current state of the state machine is equal to the string active, so only

17:04.950 --> 17:09.870
when the state machine is in the active state, we want to print the messages that we are receiving

17:09.870 --> 17:12.480
within the chatter topic into the terminal.

17:12.510 --> 17:24.290
So let's use the rcl cpp info stream function And here let's use the Get Logger function to print the

17:24.290 --> 17:33.080
message lifecycle node Earth, and then followed by the message that was just received.

17:33.080 --> 17:36.200
And so the message dot data.

17:36.200 --> 17:38.840
And convert this to a C type string.

17:39.380 --> 17:40.730
Let's save it.

17:40.880 --> 17:44.760
And with this our simple life cycle node class is completed.

17:44.760 --> 17:50.370
So we have defined all the functions that defines all the transition of the state machine.

17:50.640 --> 17:53.580
Now we can move on to define the main function.

18:06.180 --> 18:10.660
And within this function, as always, let's initialize the communication with Ros2.

18:17.080 --> 18:22.120
Then let's create a new object of type single thread executor.

18:22.330 --> 18:27.340
And so from the Clcp from the executors.

18:28.690 --> 18:37.020
Let's create a single thread executor object and let's call this 1STE here.

18:37.350 --> 18:38.970
This one is not needed.

18:39.180 --> 18:43.950
And now let's initialize a new shared pointer to an object of this class.

18:43.950 --> 18:46.530
So to this simple lifecycle node class.

18:46.770 --> 18:54.360
So let's call this variable simple lifecycle node.

18:54.360 --> 19:04.720
And let's use the function make shared to create a new shared pointer to the simple lifecycle node class.

19:05.710 --> 19:07.660
And let's assign it a name.

19:07.660 --> 19:13.060
So as you might remember, the constructor of this class requires a string as input.

19:13.060 --> 19:20.370
So the name of the node that we can call simple life cycle node.

19:20.730 --> 19:24.030
Then let's use the state object.

19:24.030 --> 19:30.090
So the single thread executor object to add a node to the single thread executor.

19:30.090 --> 19:33.720
And so let's add the simple life cycle node.

19:33.720 --> 19:37.170
And from this one we want to execute the node.

19:37.170 --> 19:42.150
So let's take the get base node interface.

19:42.880 --> 19:46.660
And now let's just keep the single thread executor up and running.

19:46.660 --> 19:51.100
So the function spin and when it finishes.

19:51.100 --> 19:57.700
So when we interrupt its execution with control C we want to shut down the communication with Ros.

19:57.700 --> 20:00.520
So let's call the shutdown function.

20:01.570 --> 20:07.710
Finally for this lesson we need to declare the dependencies and to install this new node in the Arduino

20:07.710 --> 20:09.600
bot CP examples package.

20:09.600 --> 20:13.260
So let's open the file Cmakelists.txt.

20:13.620 --> 20:16.050
And here let's add a new dependency.

20:17.100 --> 20:25.980
So with defined package, let's add a dependency from the Clcp life cycle package.

20:26.820 --> 20:28.980
And then let's add a new executable.

20:28.980 --> 20:33.100
So actually we can copy this one and we can paste it.

20:33.400 --> 20:40.720
And let's change the name of the executable into simple lifecycle node.

20:40.720 --> 20:43.300
And then the script that implements.

20:43.300 --> 20:46.870
So the source code is this one that is in the source folder.

20:46.870 --> 20:50.380
And this is called simple lifecycle node dot cpp.

20:50.800 --> 20:58.440
Then let's add the dependencies so to the simple lifecycle cycle node which uses the Clcp library.

20:58.470 --> 21:05.880
Then the Clcp life cycle and the standard messages.

21:07.050 --> 21:12.570
And then let's copy the name of this executable and let's add it into the install.

21:12.570 --> 21:17.910
So here into the install statement in which we have already installed all the previous nodes that belongs

21:17.910 --> 21:18.840
to this package.

21:19.570 --> 21:25.870
Finally, since we added few more dependencies into the CMake list, let's also declare them within

21:25.870 --> 21:27.580
the package dot XML.

21:27.580 --> 21:35.860
So here let's add a new dependency from the rcl cpp lifecycle package.

21:35.950 --> 21:40.120
And these are all the dependencies we need to conclude this lesson.

21:40.120 --> 21:46.050
Let's build our workspace to verify that there are no errors and that actually the compiler is able

21:46.050 --> 21:50.220
to generate an executable for our simple lifecycle node.

21:50.760 --> 21:56.460
So let's go to the workspace and let's build it with the command called build.

22:01.110 --> 22:02.550
The build is successful.

22:02.550 --> 22:07.170
And so in the next lesson we are going to see how to use the lifecycle nodes.
