WEBVTT

00:00.110 --> 00:07.190
In this laboratory lesson, we will create a simple ros2 lifecycle node in Python that subscribes to

00:07.190 --> 00:13.520
a topic and displays in the terminal the current state of the life cycle node, and also any message

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

00:19.130 --> 00:21.440
So back to Visual Studio Code.

00:21.590 --> 00:27.230
Since we are creating a simple code that we won't use to add a new functionality to the robot, but

00:27.230 --> 00:29.870
only to provide you with a sample code.

00:29.870 --> 00:35.960
So with a template that you can use as a starting point to create also new life cycle nodes for your

00:35.960 --> 00:36.770
projects.

00:36.890 --> 00:42.350
We are going to create this node inside the Arduino board Pi example package.

00:42.350 --> 00:50.570
And here within the Arduino board Pi example subfolder, let's create a new file and let's call it simple

00:52.250 --> 00:56.600
life lifecycle node.py.

00:56.630 --> 00:58.250
So let's create this file.

00:58.250 --> 01:02.810
And here as always let's start by importing the CLP library.

01:04.850 --> 01:07.130
And then still from this library.

01:07.130 --> 01:14.720
So from the CLP library whether to create a simple node we had to import the node class.

01:14.720 --> 01:22.190
To create a lifecycle node we need to import the node class, but this time from the life cycle module.

01:22.250 --> 01:26.120
So let's import from the life cycle.

01:26.120 --> 01:27.980
So from the RCMP life cycle.

01:28.910 --> 01:31.220
Let's import the node class.

01:31.220 --> 01:36.650
So now this node class that we are importing here is from the life cycle module.

01:36.680 --> 01:43.380
Now whether to create a simple Ros2 node we had to create a class that inherited from the node class

01:43.380 --> 01:44.940
of the CLP library.

01:44.940 --> 01:48.120
To create a life cycle node, we need to do the same.

01:48.120 --> 01:50.370
So we need to create a new class.

01:51.060 --> 01:59.850
Let's call it simple life cycle node and this one inherits from the node class.

01:59.850 --> 02:06.600
So from the life cycle node class that we have imported here from the CLP lifecycle module.

02:06.630 --> 02:10.650
Now let's start by declaring the constructor of this class.

02:11.910 --> 02:16.470
So the init function and within this constructor.

02:16.470 --> 02:22.050
So this one here takes as input a list of arguments starting with the node name.

02:22.050 --> 02:25.290
And also eventually any arguments.

02:25.290 --> 02:30.480
So k w args like this.

02:30.480 --> 02:35.940
And here within the constructor, the only thing that actually we need to do is to initialize the base

02:35.940 --> 02:36.450
class.

02:36.450 --> 02:39.810
So we need to call the constructor of the base class.

02:40.680 --> 02:46.560
So basically let's call the init function of the base class.

02:46.560 --> 02:50.670
And to this function we can pass the same arguments that we have received as input.

02:50.670 --> 02:53.940
So here the node name and the arguments.

02:53.940 --> 02:59.610
So let's provide all of them to the constructor of the simple life cycle node.

03:00.180 --> 03:05.310
At this point we have created and initialized our life cycle node class.

03:05.310 --> 03:12.090
And as we have seen in the theoretical lesson, when the life cycle node is created, it is in the unconfigured

03:12.090 --> 03:20.340
state in which we only have two transitions available that are the configure and the shutdown transition.

03:20.760 --> 03:24.810
So let's define within the simple lifecycle node class.

03:24.810 --> 03:28.650
So here the behavior of these two transitions.

03:28.650 --> 03:34.620
So the behavior of these two functions that respectively bring the state machine so the lifecycle node

03:34.620 --> 03:38.610
to the inactive state or to the finalized state.

03:40.080 --> 03:43.410
So let's start defining the on configure Unconfigure transition.

03:43.530 --> 03:48.000
So let's define the unconfigure.

03:48.420 --> 03:52.530
And this is a member of the simple lifecycle node class here.

03:52.530 --> 03:58.680
And this function takes one input that is the current state of the lifecycle node represented as an

03:58.680 --> 04:04.140
object of the state class that is still declared here in this module here.

04:04.350 --> 04:07.560
So let's import also the state class.

04:07.560 --> 04:13.020
And here the Unconfigure function receives a state as input.

04:13.020 --> 04:16.470
That is an object of type state.

04:16.470 --> 04:22.950
Now the output of this function instead represents whether or not the transition was successful.

04:22.950 --> 04:29.100
And this is represented by another object still from the RCL pi lifecycle module.

04:29.100 --> 04:31.230
And this is an object of type.

04:32.260 --> 04:34.720
transition callback return.

04:34.720 --> 04:41.920
So actually we can use this class here in order to set that the Unconfigured function will eventually

04:41.920 --> 04:46.870
return an object of type transition callback return perfect.

04:46.870 --> 04:50.680
And so now we can move on to define actually this function here.

04:50.830 --> 04:53.620
So to define the Unconfigured function.

04:53.620 --> 04:59.740
Now here within the body of this function, when this transition is activated, we want the lifecycle

04:59.740 --> 05:04.450
node to register a new subscriber to ascertain Ros2 topic.

05:04.450 --> 05:08.050
So let's create a new subscriber.

05:08.050 --> 05:15.160
So a new variable called subscriber that we can initialize with the create subscription function.

05:15.280 --> 05:20.350
So still let's use the create subscription function.

05:20.350 --> 05:24.490
This one here that actually we are still inheriting from this class here.

05:24.490 --> 05:26.200
So from the node class.

05:26.200 --> 05:31.930
And now within the create subscription function we need to indicate the type of the interface.

05:31.930 --> 05:36.640
So the type of the message that we want to receive with this subscriber.

05:36.640 --> 05:40.390
And actually we want to receive messages that are of type string.

05:40.390 --> 05:42.700
So let's import this message.

05:44.020 --> 05:55.180
So from the standard messages and from the messages let's import the string message.

05:55.180 --> 05:58.960
And now we can use it here within the create subscription function.

05:58.960 --> 06:00.520
So this receives a string.

06:00.730 --> 06:06.970
And also here in the create subscription function we need to indicate the name of the topic in which

06:06.970 --> 06:08.360
we want to subscribe.

06:08.360 --> 06:14.810
So where we want to receive the messages and let's call this one for example the chapter topic.

06:14.810 --> 06:20.720
And then we also need to set the name of the callback function that we want to execute whenever we receive

06:20.720 --> 06:22.940
a new message on this topic here.

06:23.300 --> 06:30.080
So for example, let's execute a new function called message callback.

06:30.080 --> 06:35.500
And so this function here we are going to define this function here still within the simple lifecycle

06:35.500 --> 06:36.430
node class.

06:36.430 --> 06:39.610
Also let's set the queue size of ten.

06:39.610 --> 06:41.380
So a standard queue size of ten.

06:41.560 --> 06:47.290
Now once the subscriber is correctly initialized let's also print a message in the terminal.

06:47.650 --> 06:58.720
So with the get logger function and let's print an informative message that says life cycle node.

07:01.660 --> 07:03.850
Unconfigure cold.

07:03.850 --> 07:10.000
So we are just informing the user that the unconfigure function of the life cycle node has been called.

07:10.240 --> 07:11.740
Now here.

07:11.740 --> 07:16.570
So after printing this message, let's simply return a success message.

07:16.900 --> 07:20.560
So let's return an object of type transition callback.

07:20.570 --> 07:24.620
return of type success.

07:24.620 --> 07:33.380
So this one here, and basically this success message indicates that the Unconfigure function was successfully

07:33.380 --> 07:34.040
executed.

07:34.040 --> 07:38.120
And so now the lifecycle node is in the inactive state.

07:38.270 --> 07:44.990
Now let's define also the second transition that is available while the lifecycle node is in the unconfigured

07:44.990 --> 07:45.590
state.

07:45.590 --> 07:48.440
And so this is the shut down transition.

07:48.740 --> 07:56.300
Actually, in order to define this we can copy all the Unconfigured function as basically also the on.

07:59.660 --> 08:01.910
Shutdown function will receive the same input.

08:01.910 --> 08:04.970
So we receive the state of the lifecycle node.

08:04.970 --> 08:09.230
And also we will return an object of type transition callback return.

08:09.260 --> 08:15.680
Now when we execute this transaction, we want to move the state machine of the lifecycle node into

08:15.680 --> 08:21.290
the finalized state, which is actually the state that precedes the destruction of the node.

08:21.320 --> 08:26.270
So before destroying the node, we want to deactivate the subscriber.

08:26.360 --> 08:33.170
So actually let's take the subscriber and let's just use the.

08:34.520 --> 08:37.880
destroy subscription function.

08:37.880 --> 08:41.810
So this is another function that we have inherited from the node class.

08:41.810 --> 08:45.740
And let's simply destroy the subscriber.

08:45.740 --> 08:51.770
So the sub object that we have created so that we have created and initialized previously.

08:51.770 --> 08:57.560
And also let's print an informative message in the terminal to inform the user that the on.

08:59.240 --> 09:06.050
shut down function has been correctly called and executed, and also in this case, let's return a message

09:06.050 --> 09:09.350
of type transition callback return of type success.

09:09.350 --> 09:14.480
So in order to indicate that also this transition here has been correctly executed.

09:14.930 --> 09:22.160
Now once we have configured the lifecycle node with the configure transition it enters the inactive

09:22.160 --> 09:22.910
state.

09:22.940 --> 09:29.450
In this state, there are three transitions available the clean up transition, which brings the state

09:29.450 --> 09:31.970
machine back to the unconfigured state.

09:31.970 --> 09:37.130
Then there is the shut down which we have already defined and which brings the state machine to the

09:37.130 --> 09:38.270
finalized state.

09:38.270 --> 09:44.540
And finally, we have the active state transition, which instead brings the state machine to the active

09:44.540 --> 09:45.200
state.

09:45.260 --> 09:48.810
Now let's start by defining the clean up transition.

09:48.810 --> 09:52.410
So the one that brings the note back to the unconfigured state.

09:52.980 --> 09:58.200
Actually, to define this function, we can simply copy the on shut down function.

09:58.710 --> 10:00.660
And we can paste it here.

10:00.660 --> 10:06.120
Let's call it on clean up.

10:06.120 --> 10:13.020
So also this one receives an object of type state and also will return an object of type transition

10:13.020 --> 10:14.160
callback return.

10:14.280 --> 10:20.880
Now in this function, we can still destroy the subscription, so we can still destroy the subscriber

10:20.880 --> 10:25.200
that we have initialized here in the Unconfigured transition.

10:25.200 --> 10:34.230
And also here let's print a different message in the terminal that says on clean up called.

10:34.260 --> 10:37.140
And also in this case let's return success.

10:37.680 --> 10:41.040
Let's also define the on activate function.

10:41.040 --> 10:44.730
So again we can copy the on clean up function.

10:44.760 --> 10:46.080
We can paste it.

10:46.080 --> 10:51.600
Let's rename it to on activate.

10:51.600 --> 10:53.790
And when this function is executed.

10:53.790 --> 11:00.450
So the activate transition we want to print a message in the terminal that informs that this function

11:00.450 --> 11:01.290
is being called.

11:01.290 --> 11:09.540
So we can remove this line here, and we can simply print the message on activate called.

11:09.540 --> 11:14.790
And also in this function here, in order to simulate the execution of various operations.

11:14.790 --> 11:18.360
For example, when activating a real life cycle node.

11:18.360 --> 11:20.160
Let's pause this thread.

11:20.160 --> 11:23.430
So let's pause this current thread for a few seconds.

11:23.430 --> 11:29.940
So here let's import also another Python library that is the time library.

11:29.940 --> 11:38.790
And here before actually returning the success, let's simply sleep for two seconds.

11:38.790 --> 11:42.270
Let's say perfect also when we call.

11:42.270 --> 11:48.870
So when we execute the on activate function, let's also call the on activate function for the parent.

11:48.870 --> 11:52.140
So for the base class that is the node class.

11:52.350 --> 12:01.770
So let's use the super in order to execute the UN activate function for the parent.

12:01.770 --> 12:06.240
And this one receives as input the same state that we have here.

12:09.210 --> 12:16.020
And actually we can just take this one and we can paste it here.

12:16.140 --> 12:20.850
So actually we can return the result of this operation here.

12:20.850 --> 12:23.850
So the result of the activation of the base class.

12:24.240 --> 12:26.040
Now once in this state.

12:26.040 --> 12:31.020
So once in the active state there are only two transitions available.

12:31.020 --> 12:36.880
The shutdown transition which we have already defined and implemented and which brings the state machine

12:36.880 --> 12:39.040
into the finalized state.

12:39.040 --> 12:45.850
And then we have the on deactivate, which instead brings the life cycle node back to the inactive state.

12:45.880 --> 12:50.410
So let's define this last transition that is the on deactivate.

12:50.410 --> 12:53.410
So actually we can just copy the on activate.

12:53.920 --> 12:57.730
And let's change its name to be on deactivate.

12:59.020 --> 13:03.250
And and inside this function, let's change the message that we are printing.

13:03.250 --> 13:05.920
That is the on the activate cold.

13:05.920 --> 13:06.730
Perfect.

13:06.730 --> 13:11.230
And here actually we don't need the sleep so we can remove it.

13:11.230 --> 13:18.340
And also in this case so also when we deactivate the lifecycle node let's call the on the activate.

13:18.340 --> 13:21.010
So let's deactivate also the base class.

13:21.010 --> 13:26.110
So basically we are deactivating also the base class from which we are inheriting.

13:26.560 --> 13:31.150
With this we have defined the behavior of all the transition of the lifecycle node.

13:31.180 --> 13:36.730
Now the only thing that is left to do is to define the message callback function.

13:36.730 --> 13:42.790
So this last function here that is executed whenever we receive a new message on the chatter topic.

13:43.390 --> 13:48.370
So let's define the message callback.

13:48.370 --> 13:57.520
This is still a member of the simple life cycle node class, and as it is a callback function, it receives

13:57.520 --> 14:00.670
the message that was just received in the topic.

14:00.700 --> 14:06.730
Now, when we receive a new message on the chapter topic, we first want to check whether the current

14:06.730 --> 14:13.600
state of the life cycle node is active or not in order to decide whether to display the message.

14:13.600 --> 14:22.630
So to do so, let's take the state machine so we can access to the state machine that we have inherited

14:22.630 --> 14:23.920
from the node class.

14:23.920 --> 14:30.310
And actually we can access to the current state of the state machine.

14:30.310 --> 14:36.490
And actually in order to access to the current state easily, let's save this one into a new variable

14:36.490 --> 14:39.670
that we are simply calling current state.

14:39.700 --> 14:42.820
And now let's check this current state.

14:42.820 --> 14:51.850
So if the current state and actually the first element is of type active.

14:51.850 --> 14:54.820
So if the state machine is in the active state.

14:54.820 --> 15:02.680
So if the name of the current state is active in this case we just want to print an informative message

15:02.680 --> 15:06.490
in the terminal with the content of the message that we have just received.

15:07.240 --> 15:16.840
So let's get the logger and let's print an informative message that says I erred.

15:17.080 --> 15:19.210
Followed by the actual message.

15:19.210 --> 15:26.930
So this is a string and let's print the content of the message data.

15:26.930 --> 15:27.890
So perfect.

15:27.890 --> 15:29.540
This one is a string message.

15:29.540 --> 15:35.030
And so here we are simply printing the content of the message that we have received otherwise.

15:35.030 --> 15:40.820
So if the current state of the state machine is not active so is any of the other states.

15:40.820 --> 15:43.100
We are just not going to print anything.

15:43.100 --> 15:45.650
So we are just ignoring the message.

15:45.890 --> 15:46.760
Perfect.

15:46.760 --> 15:52.730
So with this we have completed our simple life cycle node class and so we can move on to define the

15:52.730 --> 15:53.960
main function.

15:53.990 --> 15:59.930
So here let's define the main function.

16:06.230 --> 16:11.690
And so we are missing to define the function main like this.

16:11.690 --> 16:12.710
Perfect.

16:13.580 --> 16:20.060
And here within this function, let's start by initializing rcl py with rcl py.

16:20.930 --> 16:21.620
Init.

16:21.890 --> 16:26.750
And then let's create a new object of type single thread executors.

16:26.750 --> 16:30.680
So let's call this one executor.

16:30.680 --> 16:41.480
And from the RCMP executors module let's create a new object of type single thread executor.

16:41.480 --> 16:46.910
And also we can create a new instance of the simple single node that we have just created.

16:47.030 --> 16:50.720
So simple lifecycle node.

16:50.720 --> 16:54.470
And this is an instance of the simple lifecycle node.

16:54.470 --> 16:56.780
And now the constructor.

16:56.780 --> 17:02.540
As you remember the constructor of this class here takes as input the name of the node.

17:02.540 --> 17:06.110
So for example, let's call it simple.

17:08.210 --> 17:10.160
Life cycle node.

17:10.160 --> 17:11.090
Perfect.

17:11.090 --> 17:17.390
And now we simply in order to execute actually this node here we can add it to this executor.

17:17.390 --> 17:22.490
So with executor let's add a node.

17:22.490 --> 17:27.650
And the node that we want to add is the simple life cycle node that we have just created.

17:27.770 --> 17:30.860
Now in order to keep this node up and running.

17:30.860 --> 17:39.830
So in order to start actually the execution of this node, let's try to execute the function executor

17:40.880 --> 17:41.750
spin.

17:41.750 --> 17:42.710
Perfect.

17:42.710 --> 17:45.080
And if there is an exception.

17:45.080 --> 17:50.390
So if for example there is a keyboard interrupt.

17:50.390 --> 17:58.070
So if we stop manually the execution of the node with Ctrl C or for example, if there is an error of

17:58.070 --> 18:05.630
type rcl pi executors external shutdown exception.

18:05.630 --> 18:09.590
So in other cases we want to simply destroy the node.

18:09.710 --> 18:15.660
So let's take the simple lifecycle node and let's destroy it.

18:15.660 --> 18:21.720
So let's use the function destroy node perfect so we can save this file.

18:21.720 --> 18:27.510
And finally to conclude this lesson let's install the new node that we have just created.

18:27.510 --> 18:34.200
So let's go to the file setup.py that is in the Arduino bot Pi examples package here.

18:34.200 --> 18:40.830
Actually we can copy one of the instructions that we've already used, and we can paste it here at the

18:40.830 --> 18:41.730
end of the file.

18:41.730 --> 18:49.230
So the new node that we are creating is called simple lifecycle node.

18:49.230 --> 18:58.020
And the script is in the Arduino Pi examples folder is called simple lifecycle node.

18:58.020 --> 19:01.590
And also from this node we want to execute the main function.

19:01.800 --> 19:03.810
Now let's save also this file.

19:03.810 --> 19:07.500
And to conclude this lesson let's simply apply these changes.

19:07.500 --> 19:09.450
So let's simply make this new node.

19:09.450 --> 19:12.210
So the simple cycle node and executable.

19:12.390 --> 19:15.270
So to do so let's open a new terminal.

19:15.900 --> 19:19.800
Let's go to the workspace and let's build it.
