1
00:00:00,810 --> 00:00:06,600
Usually these course lessons are set up so that I spend one lecture going over a topic and then we have

2
00:00:06,600 --> 00:00:08,280
a project which applies it.

3
00:00:08,520 --> 00:00:14,520
In this lesson, we will be doing a project walkthrough to explain how to utilize actions in ROS two.

4
00:00:14,940 --> 00:00:19,680
ROS actions are not inherently complicated, but they have a lot of parts to them, so it's easier to

5
00:00:19,680 --> 00:00:23,910
understand when taught in the context of using it in a robotics project.

6
00:00:24,600 --> 00:00:30,060
As you learned in the Ross two overview video, Ross actions is another inter node communication method.

7
00:00:30,800 --> 00:00:38,690
Here in action goal is similar to a service request and an action result is similar to a service response.

8
00:00:38,900 --> 00:00:44,810
The feedback aspect of an action is similar to a publisher in which it keeps publishing feedback relevant

9
00:00:44,810 --> 00:00:48,110
to the state of the goal until a result is reached.

10
00:00:48,680 --> 00:00:54,320
In this project walkthrough, we will program similar functionality shown in the Ross overview animation.

11
00:00:54,560 --> 00:00:59,600
The project scenario is that we have a mobile camera robot and we want to send it to coordinates to

12
00:00:59,600 --> 00:01:01,520
a goal point to navigate to.

13
00:01:01,790 --> 00:01:07,010
While the robot is navigating, we want it to send feedback on how far away it is from the goal point.

14
00:01:07,850 --> 00:01:13,190
When it reaches the goal, the robot then returns a result of how long it took to get to that goal point.

15
00:01:13,960 --> 00:01:17,470
This is an ideal situation where we'd want to use a Ross action.

16
00:01:17,500 --> 00:01:22,120
It has a goal aspect, feedback aspect and a result aspect to it.

17
00:01:23,210 --> 00:01:27,980
An additional thing to note is we will also need to create a publisher to simulate a position sensor,

18
00:01:27,980 --> 00:01:30,470
which tells us the current point the robot is at.

19
00:01:30,770 --> 00:01:35,360
Our action will find the distance between the goal point and the simulated current position to send

20
00:01:35,360 --> 00:01:38,360
feedback on how far the robot is from the goal.

21
00:01:39,040 --> 00:01:41,720
OC now lets you understand the idea behind this project.

22
00:01:41,720 --> 00:01:43,250
Let's walk through it together.

23
00:01:44,060 --> 00:01:47,390
All right, so how do we go about creating a custom action interface?

24
00:01:47,420 --> 00:01:50,400
Well, it's quite similar to how we create our service.

25
00:01:50,420 --> 00:01:53,190
First, we need to create a custom action file.

26
00:01:53,210 --> 00:01:55,400
This will be placed within our package folder.

27
00:01:55,580 --> 00:01:57,650
So create a new folder called Action.

28
00:01:59,030 --> 00:02:02,420
And then within this folder, we can create our actual action file.

29
00:02:02,450 --> 00:02:05,510
In this case, I'm going to call Mines Navigate Action.

30
00:02:06,470 --> 00:02:11,510
There are three sections to this file, and I'll quickly comment out here to make this file more readable.

31
00:02:11,600 --> 00:02:13,320
The first is the goal.

32
00:02:13,340 --> 00:02:17,180
Just like services, we will separate each section with three dashes.

33
00:02:17,420 --> 00:02:21,770
The second section is the result and the third section is the feedback.

34
00:02:23,680 --> 00:02:27,520
My goal for this project is a point at which I want the robot to travel to.

35
00:02:27,550 --> 00:02:33,550
I'll do this project in terms of a2d grid with X and Y coordinates, rather than creating a custom x

36
00:02:33,550 --> 00:02:39,130
and Y part to my request, I can do a quick search to see Ross message types available that may be able

37
00:02:39,130 --> 00:02:39,850
to help me.

38
00:02:39,970 --> 00:02:45,460
So here I'll be using the point message type from the Geometry Messages library.

39
00:02:45,460 --> 00:02:52,420
So we see this pre-made message type has a float 64 data type for x, y and z coordinates, so I'll

40
00:02:52,420 --> 00:02:55,810
go ahead and utilize this rather than creating something custom.

41
00:02:56,550 --> 00:02:58,050
To include this in my action file.

42
00:02:58,050 --> 00:03:04,440
I will simply type geometry messages slash point that I'll name the attribute of my goal Goal point.

43
00:03:04,560 --> 00:03:09,060
My result of this action will be the time that elapsed while I travel to the goal point.

44
00:03:09,060 --> 00:03:14,400
I could use one of Roz's time based messages, but I'll keep it simple and just use a float 32 message

45
00:03:14,400 --> 00:03:17,220
type to return back the elapsed time in seconds.

46
00:03:18,270 --> 00:03:22,860
Lastly, my feedback will just be the distance my robot is from the goal point.

47
00:03:22,860 --> 00:03:28,590
I can also keep this simple and just use a float 32 message type to return back the distance in meters.

48
00:03:28,590 --> 00:03:31,170
Assuming that my grade I use is in meters.

49
00:03:31,380 --> 00:03:31,890
Great.

50
00:03:31,890 --> 00:03:35,880
That's all we need for our action file, but we'll need to make sure that our package is set up to create

51
00:03:35,880 --> 00:03:37,470
custom action interfaces.

52
00:03:37,470 --> 00:03:43,620
If I open up my package XML file, I already have most of the penances I need from these services lecture

53
00:03:43,620 --> 00:03:47,730
related to creating custom interfaces by including Ross Idle.

54
00:03:47,730 --> 00:03:54,120
The only additional thing I need to do is add dependencies for action messages like so that's it.

55
00:03:54,120 --> 00:03:56,220
I can save this file and close it.

56
00:03:57,070 --> 00:04:02,200
Now I'll have to head over to my quick List text file to let it know that I've made a new interface

57
00:04:02,200 --> 00:04:03,700
that it needs to compile.

58
00:04:03,880 --> 00:04:06,850
All I need to do for this is to add the file to my ROS.

59
00:04:06,850 --> 00:04:09,130
Idle generate interfaces function.

60
00:04:09,130 --> 00:04:13,480
In this case it is action forward slash navigate dot action.

61
00:04:14,200 --> 00:04:20,800
Now I'm also using the geometry messages package as a dependency for my action, so I'll be sure to

62
00:04:20,800 --> 00:04:26,200
include that as a dependency here as well as to find the package above.

63
00:04:33,610 --> 00:04:34,000
All right.

64
00:04:34,000 --> 00:04:40,570
With that, I can save the file and be sure to save my action file, and we can go ahead and rebuild

65
00:04:40,570 --> 00:04:41,620
our workspace.

66
00:04:52,210 --> 00:04:52,420
No.

67
00:04:52,420 --> 00:04:55,570
And I made a typo here where I forgot the s and geometry messages.

68
00:04:55,570 --> 00:05:01,030
So I'll just go ahead and save that and we can go ahead and try to rebuild.

69
00:05:07,710 --> 00:05:08,340
All right, great.

70
00:05:08,340 --> 00:05:11,250
So we successfully built our workspace.

71
00:05:11,250 --> 00:05:15,150
So now if I head over to terminal, we can go ahead and source it.

72
00:05:19,520 --> 00:05:29,990
And now if we do Ross to interface lists under actions, we see our Udemy rescue package action navigate

73
00:05:29,990 --> 00:05:31,310
that we just created.

74
00:05:31,340 --> 00:05:37,760
As usual, you can always find information about the action by doing Ross to interface show followed

75
00:05:37,760 --> 00:05:38,840
by the name of the action.

76
00:05:41,320 --> 00:05:45,960
And Odysseus has gone ahead and broke down our action into its dependency core components.

77
00:05:45,970 --> 00:05:51,940
So here we are using the geometry messages, Gold Point, and it broke it into its individual X, y,

78
00:05:51,940 --> 00:05:53,050
z coordinates.

79
00:05:53,050 --> 00:05:58,180
So that's one of the cool things when utilizing that interface show command is you'll always get the

80
00:05:58,180 --> 00:06:02,230
base units for each of the corresponding attributes.

81
00:06:02,470 --> 00:06:02,740
Great.

82
00:06:02,740 --> 00:06:08,590
With that, I'll go ahead and clear the screen and we can go ahead and start working on our action server

83
00:06:09,400 --> 00:06:14,040
so I can go over the scripts, create a new file and I'll just call it Action Server PI.

84
00:06:17,120 --> 00:06:21,170
We we'll start this group the same way we have been doing throughout the course by including our Ross

85
00:06:21,180 --> 00:06:23,670
modules and creating our main function.

86
00:06:23,880 --> 00:06:27,870
To make this quicker, I'll simply copy and paste the contents of our original publisher note.

87
00:06:44,270 --> 00:06:49,100
So here, I'll just go ahead and change the class name to navigate action server.

88
00:06:51,420 --> 00:06:54,450
And change some of the corresponding print statements and variable names.

89
00:07:09,010 --> 00:07:14,620
In order to use the action server functionality, we must import it in from RC LP action.

90
00:07:16,210 --> 00:07:19,310
Then we will include our custom action interface we just made.

91
00:07:19,330 --> 00:07:22,570
We do that the same way we did with our custom service interface.

92
00:07:23,260 --> 00:07:28,330
Now we will create an action server variable within our class, which we will set equal to the Ross

93
00:07:28,330 --> 00:07:29,950
Action Server object.

94
00:07:30,220 --> 00:07:34,180
A common thing programmers do within class variables is start them with an underscore if the name is

95
00:07:34,180 --> 00:07:36,280
similar to other content in the code.

96
00:07:36,310 --> 00:07:39,700
That is why I name this variable underscore action server as that way.

97
00:07:39,700 --> 00:07:43,300
We know this is associated with the classes action server.

98
00:07:43,660 --> 00:07:46,420
Now the action server call takes in four parameters.

99
00:07:46,420 --> 00:07:47,620
The first is the node.

100
00:07:47,620 --> 00:07:51,730
This navigate action server class is the node, so we can just pass in self.

101
00:07:51,910 --> 00:07:57,450
Then we'll pass in the action interface, which in this case is my custom navigate action.

102
00:07:57,460 --> 00:08:01,600
And the third argument is the action topic name, which I'll just call navigate.

103
00:08:01,960 --> 00:08:06,160
Then the fourth argument is the callback, which runs when we receive a goal from the client, which

104
00:08:06,160 --> 00:08:08,260
I will just call, navigate, callback.

105
00:08:08,380 --> 00:08:10,900
Let's go ahead and create this callback function below.

106
00:08:10,990 --> 00:08:14,950
It will take in self and the action server goal handle.

107
00:08:15,040 --> 00:08:18,790
I'll just start this off by printing out that we got a goal.

108
00:08:20,500 --> 00:08:24,700
So the main thing we're going to want to do from the goal is to unload the point we received from the

109
00:08:24,700 --> 00:08:25,340
client.

110
00:08:25,360 --> 00:08:31,090
So I will create a variable called robot goal point and set it equal to a list.

111
00:08:31,090 --> 00:08:37,630
And I will unload each coordinate by calling goal handle dot request followed by the name of our request

112
00:08:37,630 --> 00:08:42,340
attribute, which, as you can see in my navigate action file, I called it Goal point.

113
00:08:42,430 --> 00:08:44,920
Then I will specify the x coordinate.

114
00:08:45,400 --> 00:08:49,180
I can then go ahead and do this for the Y and z coordinates as well.

115
00:08:49,960 --> 00:08:55,300
Then to make sure we are unloading this request correctly, I will just print it out to our terminal.

116
00:09:01,470 --> 00:09:01,700
OC.

117
00:09:02,150 --> 00:09:03,350
We have our goal now.

118
00:09:03,350 --> 00:09:08,390
We need to calculate the distance between the current position of our robot and this goal position.

119
00:09:08,570 --> 00:09:13,190
In order to get the current position of the robot, I will need to first create a subscriber to get

120
00:09:13,190 --> 00:09:14,630
the current robot position.

121
00:09:14,810 --> 00:09:20,000
To do this I will create a sub variable and use the create subscription function of our node.

122
00:09:20,270 --> 00:09:25,670
This subscriber will subscribe to a point message from the Geometry Messages library, so I will need

123
00:09:25,670 --> 00:09:27,080
to include it above.

124
00:09:27,290 --> 00:09:32,570
Then I'll just say the topic I will be subscribing to is robot position and each time a message gets

125
00:09:32,570 --> 00:09:36,950
published to the topic we will run a callback function called update Robot position.

126
00:09:37,070 --> 00:09:42,200
Then I will set my queue size, which I'll just set to one as I only want to process the latest position

127
00:09:42,200 --> 00:09:42,920
received.

128
00:09:43,100 --> 00:09:45,290
Now let's create this callback function.

129
00:09:45,290 --> 00:09:50,750
All I wanted to do is update the variable accessible by my action server callback function, so I'll

130
00:09:50,750 --> 00:09:53,830
just create a variable called robot current position.

131
00:09:53,840 --> 00:09:54,800
This will be a list.

132
00:09:54,800 --> 00:09:57,530
I unload the x and y and z coordinates of the point to.

133
00:10:01,660 --> 00:10:02,560
For good measure.

134
00:10:02,560 --> 00:10:07,060
I should make sure we don't try to calculate the distance if we have not received the robot current

135
00:10:07,060 --> 00:10:07,780
position.

136
00:10:07,900 --> 00:10:13,720
So I will initialize this variable as none and in my action server callback make a while loop which

137
00:10:13,720 --> 00:10:16,450
will hold us up until we receive a point.

138
00:10:16,600 --> 00:10:19,930
I will simply prompt the user that our robot point was not detected.

139
00:10:21,020 --> 00:10:26,120
And I will use the spin one function which takes in our node and I can set the time out period, which

140
00:10:26,120 --> 00:10:28,070
I'll just set to 3 seconds.

141
00:10:28,950 --> 00:10:34,230
Now there's a specific reason why I use the spin one function instead of importing the time Python module

142
00:10:34,230 --> 00:10:35,880
and calling time sleep.

143
00:10:35,910 --> 00:10:40,890
If you use time sleep, it will block any other code from being run until it finishes and then it will

144
00:10:40,890 --> 00:10:42,240
continue its current thread.

145
00:10:42,330 --> 00:10:47,040
That means our subscriber would be blocked and never be able to receive any new messages.

146
00:10:47,040 --> 00:10:51,960
So we use the spin of one function within this while loop, which basically allows all of our other

147
00:10:51,960 --> 00:10:55,260
ROS functionality within this class to run while we wait.

148
00:10:55,380 --> 00:11:00,450
However, this brings up another problem as we are currently using this spin function in our main function.

149
00:11:00,990 --> 00:11:05,190
Calling spin once within a spin instance will generally lead to getting stuck.

150
00:11:05,190 --> 00:11:12,690
To prevent this, I will instead replace this spin call with spin once call within a while loop and

151
00:11:12,690 --> 00:11:17,920
I will use the kl pi oc function which returns true as long as this node is not killed.

152
00:11:17,940 --> 00:11:19,740
To keep this while loop running.

153
00:11:20,600 --> 00:11:24,530
If you're interested in learning more about how spending affects your code, feel free to check out

154
00:11:24,530 --> 00:11:26,670
the code API documentation on it.

155
00:11:26,690 --> 00:11:31,310
While we are here within the ACCEPT statement, I will also call the destroy method of our action server

156
00:11:31,310 --> 00:11:33,770
variable within the node class.

157
00:11:33,770 --> 00:11:38,750
This will properly shut down the action server by letting any clients who sent requests know it's shutting

158
00:11:38,750 --> 00:11:41,480
down before the node goes and terminates.

159
00:11:41,810 --> 00:11:46,910
All right, let's go back up to our callback function where we're processing our navigate callback.

160
00:11:47,150 --> 00:11:51,710
Once our subscriber receives the robot position, we can go ahead and calculate the distance between

161
00:11:51,710 --> 00:11:52,580
the two of them.

162
00:11:52,610 --> 00:11:55,550
I can do this with the math libraries dist function.

163
00:12:01,990 --> 00:12:07,000
Then we're going to want to publish feedback of this distance until it is zero, or at least within

164
00:12:07,000 --> 00:12:08,890
a certain threshold distance.

165
00:12:09,040 --> 00:12:14,380
I'll just set a constant called distance threshold at the top of my script and I'll just set it equal

166
00:12:14,380 --> 00:12:21,460
to 0.125, which means that if I'm within 0.125 meters of the goal, I am basically at the goal and

167
00:12:21,460 --> 00:12:22,790
I've achieved this action.

168
00:12:22,810 --> 00:12:28,090
I will implement this constant in a while loop like so to keep calculating the distance until we get

169
00:12:28,090 --> 00:12:29,830
within the distance threshold.

170
00:12:30,550 --> 00:12:35,680
Every time we loop, I'm going to recalculate the distance and fill the feedback message with the data.

171
00:12:35,680 --> 00:12:39,730
So I will initialize the feedback message variable right before the while loop.

172
00:12:40,480 --> 00:12:40,810
Great.

173
00:12:40,840 --> 00:12:43,030
So in this while loop, we'll recalculate the distance.

174
00:12:43,030 --> 00:12:48,670
So I'll just go ahead and copy this over and then take the feedback message and load the distance data

175
00:12:48,670 --> 00:12:53,140
into the distance of the point attribute, which I named in my action file.

176
00:12:54,960 --> 00:13:00,750
Then to publish the feedback, I will call the goal handle publish feedback method which takes in the

177
00:13:00,750 --> 00:13:02,160
feedback message object.

178
00:13:03,330 --> 00:13:08,820
Then just so that we are not publishing feedback as quickly as possible, we can use the spin once function

179
00:13:08,820 --> 00:13:14,730
again to essentially set a rate to publish the feedback without blocking the operations of our subscriber.

180
00:13:14,760 --> 00:13:17,760
I'll set the timeout second rate to one second.

181
00:13:17,790 --> 00:13:18,360
All righty.

182
00:13:18,360 --> 00:13:19,940
That about wraps it up for feedback.

183
00:13:19,950 --> 00:13:23,850
Now we can move on to sending the result once we reach the goal point.

184
00:13:24,060 --> 00:13:28,920
The first thing I will do once this happens is called the Goal Handle success method to let the action

185
00:13:28,920 --> 00:13:30,860
server know the goal was processed successfully.

186
00:13:30,870 --> 00:13:33,480
Then we will initialize our result message.

187
00:13:35,220 --> 00:13:37,320
Then we need to fill the result message.

188
00:13:37,320 --> 00:13:40,270
With this time elapsed from when we started the goal.

189
00:13:40,290 --> 00:13:45,780
So first I will create a variable at the beginning of our action server callback called start time.

190
00:13:45,990 --> 00:13:51,650
You could use the time library for this, but I'll just use the get clock now method of our node.

191
00:13:51,660 --> 00:13:56,580
In order to use this properly, you also need to convert this from the time object to a message.

192
00:13:56,580 --> 00:13:58,770
So I will call to message.

193
00:13:59,400 --> 00:14:05,700
Then I'll go back down and load the elapsed time attribute of my result message as I specified in my

194
00:14:05,700 --> 00:14:10,680
action file and set it equal to the current time minus the start time and I'll make sure the result

195
00:14:10,680 --> 00:14:13,230
is a float by wrapping it within float tags.

196
00:14:14,620 --> 00:14:18,640
Last but not least, to close this whole function out, we will return the result.

197
00:14:21,280 --> 00:14:24,430
With that, our action server node is complete.

198
00:14:24,610 --> 00:14:26,860
So let's go ahead and review what's going on here.

199
00:14:27,070 --> 00:14:33,670
We created an action server which triggers the navigate callback to process the goal and a subscriber

200
00:14:33,670 --> 00:14:36,130
to update the robot's current position variable.

201
00:14:36,340 --> 00:14:42,310
Within our navigate to callback, we receive the data from the goal request check to make sure we receive

202
00:14:42,310 --> 00:14:47,650
the robot's current position, calculated the distance between the robot and the goal, and then continue

203
00:14:47,650 --> 00:14:51,850
to publish that as feedback until we are within the distance threshold.

204
00:14:51,850 --> 00:14:57,880
Once within the distance threshold we call the goal handle success method load our result message data

205
00:14:57,880 --> 00:14:58,970
and return it.

206
00:14:58,990 --> 00:15:04,210
Along the way, we also discussed how to utilize the spin one's function to make sure we are not blocking

207
00:15:04,210 --> 00:15:05,950
other parts of our node from running.

208
00:15:06,070 --> 00:15:11,020
In this case, all of our while loops within our action callback used spin ones to make sure we did

209
00:15:11,020 --> 00:15:13,360
not block the subscriber from receiving new data.

210
00:15:13,600 --> 00:15:14,080
Great.

211
00:15:14,080 --> 00:15:15,760
With that, let's save the file.

212
00:15:17,050 --> 00:15:21,940
Oh, and I made a mistake here where I forgot to put a comma after my point y.

213
00:15:21,940 --> 00:15:22,690
And there we go.

214
00:15:22,690 --> 00:15:26,440
Now, that got rid of that particular error with my python list.

215
00:15:26,710 --> 00:15:31,870
So let's go ahead and save the file and we can move on to the action client node.

216
00:15:31,900 --> 00:15:35,830
So I'll go into my scripts folder, create a new file, and I'll just call it Action Client.

217
00:15:36,010 --> 00:15:36,470
Bye.

218
00:15:38,110 --> 00:15:43,780
So from here, I'll just go ahead and copy the majority of the content within our action server node.

219
00:15:49,660 --> 00:15:53,650
But I can go ahead and delete most of the content within the class.

220
00:15:59,470 --> 00:16:05,020
And I can get rid of our distance threshold, constant variable, and go ahead and rename our navigate

221
00:16:05,020 --> 00:16:09,580
action server class and node names to represent a client instead.

222
00:16:22,000 --> 00:16:27,100
And since this client won't have a server to destroy, I can just go ahead and delete that line as well.

223
00:16:27,760 --> 00:16:34,030
So all we have here is our corresponding imports to utilize our actions a base node class, and then

224
00:16:34,030 --> 00:16:37,640
we're initializing our RC LP and creating our node.

225
00:16:37,670 --> 00:16:41,350
So the only thing I'll change here is instead of importing action server, we'll instead of be using

226
00:16:41,350 --> 00:16:42,640
the action client.

227
00:16:43,330 --> 00:16:46,480
So let's go ahead and initialize this client within our node class.

228
00:16:49,750 --> 00:16:54,910
So in this case we'll need to specify the node, which in this case is self, the message type, which

229
00:16:54,910 --> 00:17:00,940
will be our custom navigate interface and then the action topic name, which just has to match our server,

230
00:17:00,940 --> 00:17:02,290
which was navigate.

231
00:17:04,980 --> 00:17:08,700
Oh, and I'm getting an error here because my indentations are not matching up, so I'll be sure to

232
00:17:08,700 --> 00:17:09,570
configure that.

233
00:17:12,450 --> 00:17:12,910
Here we go.

234
00:17:12,930 --> 00:17:16,380
Now let's go ahead and create a function to send out our desired goal.

235
00:17:16,410 --> 00:17:22,500
I'll just call mine, send goal, and I'll make it so that the user can pass the x and Y coordinates

236
00:17:22,500 --> 00:17:23,670
to set the goal too.

237
00:17:24,450 --> 00:17:29,340
The first thing we will do is create our goal message and load it in with the data we want to send.

238
00:17:29,430 --> 00:17:34,020
Make sure the data you load in it matches the data type you set in your action file.

239
00:17:34,140 --> 00:17:37,230
In this case, I'm making sure all my inputs are floats.

240
00:17:47,470 --> 00:17:47,650
OC.

241
00:17:47,770 --> 00:17:52,420
We loaded our goal message data, but before we send the message, we should make sure the server is

242
00:17:52,420 --> 00:17:57,160
ready to receive it, which we can do with the wait for server function of our action client.

243
00:17:59,640 --> 00:18:02,610
Once the server is ready, we can send the goal to do so.

244
00:18:02,610 --> 00:18:07,140
I will use the send goal async function which returns a future variable.

245
00:18:07,440 --> 00:18:12,060
This function takes in the goal message we just made and we have some other optional parameters.

246
00:18:12,180 --> 00:18:17,880
I will use the feedback callback parameter to specify a callback function I will make to process the

247
00:18:17,880 --> 00:18:20,100
feedback we get back from the action server.

248
00:18:20,670 --> 00:18:23,160
We can now create this feedback callback function below.

249
00:18:23,310 --> 00:18:25,320
This takes in the feedback message.

250
00:18:29,110 --> 00:18:32,490
Whoops, I've gone ahead and have this in the wrong indentation spot.

251
00:18:32,590 --> 00:18:33,670
Let's go ahead and do that.

252
00:18:34,330 --> 00:18:39,280
In order to get the actual feedback object, we will need to call the dot feedback attribute of the

253
00:18:39,280 --> 00:18:40,000
message.

254
00:18:40,030 --> 00:18:43,870
For the most part, all I'm going to do with this feedback is print it to the terminal.

255
00:18:43,990 --> 00:18:49,630
So I will print the feedback distance to point, which is my attribute name in my action file.

256
00:18:51,610 --> 00:18:56,740
We have our feedback function set up, but what happens when the action server finishes processing the

257
00:18:56,740 --> 00:18:57,530
request?

258
00:18:57,550 --> 00:19:03,490
Well, I can take the future variable from our send request and call the add done callback function,

259
00:19:03,490 --> 00:19:05,290
which takes in a callback function.

260
00:19:05,440 --> 00:19:09,100
I will create a new function called goal response callback.

261
00:19:10,210 --> 00:19:13,450
Now I'll create the function below which takes in the future object.

262
00:19:21,960 --> 00:19:26,940
Now this is where things can get a bit confusing before we can process the result the action server

263
00:19:26,940 --> 00:19:27,240
sends.

264
00:19:27,240 --> 00:19:32,520
After processing our goal, we need to first make sure that it is first accepted our goal so the server

265
00:19:32,520 --> 00:19:34,890
will send us this response to tell us so.

266
00:19:35,040 --> 00:19:39,690
So from this future object, I'm interested in getting the goal handle which will be given to us if

267
00:19:39,690 --> 00:19:41,730
we call the result method of it.

268
00:19:43,320 --> 00:19:45,270
Keep in mind this is not the result of our action.

269
00:19:45,270 --> 00:19:49,140
This is the result of a future object when it's done being processed.

270
00:19:49,140 --> 00:19:51,810
So now we need to check if the goal was accepted.

271
00:19:51,810 --> 00:19:57,180
So I will create an IF statement to see if the accepted attribute of our goal handle is true or false.

272
00:19:57,180 --> 00:20:01,380
If it's false, then I'll prompt the user that the goal was rejected and we will want to break out of

273
00:20:01,380 --> 00:20:01,950
this function.

274
00:20:01,950 --> 00:20:04,440
So I'll just return none else.

275
00:20:04,440 --> 00:20:05,970
We are just going to carry on.

276
00:20:05,970 --> 00:20:11,880
I will print that the goal was accepted just so our user knows Now I will need to create another future

277
00:20:11,880 --> 00:20:16,560
variable to retrieve the future object from the result portion of the goal handle.

278
00:20:17,100 --> 00:20:22,800
Now, just like we added a done callback for my single future, I will need to do so for this get result

279
00:20:22,800 --> 00:20:23,550
future.

280
00:20:23,730 --> 00:20:27,300
I'll just create a callback function called Get result callback.

281
00:20:30,450 --> 00:20:33,300
This callback will actually be responsible for processing the result.

282
00:20:33,300 --> 00:20:36,330
We get back from the action server when it completes the goal.

283
00:20:36,450 --> 00:20:41,760
It will be set up similarly to that of our goal response callback in the sense that it takes the future

284
00:20:41,760 --> 00:20:46,890
object and we will need to get the result of the future object, which will also contain the actual

285
00:20:46,890 --> 00:20:48,710
result of the action.

286
00:20:48,720 --> 00:20:53,820
So also a bit confusing here as far as wording goes, but essentially we're getting the result of the

287
00:20:53,820 --> 00:20:58,410
future object, which in this case has the result of our action.

288
00:20:58,440 --> 00:21:01,510
Then lastly, I will process this result message.

289
00:21:01,530 --> 00:21:04,590
As usual, I am simply just going to print it to the terminal.

290
00:21:04,710 --> 00:21:09,780
I will print the elapsed time attribute of the result, which is the attribute name I gave it in my

291
00:21:09,780 --> 00:21:10,740
action file.

292
00:21:12,420 --> 00:21:16,740
Oh, and I made a typo here where I capitalized my result in get result callback.

293
00:21:20,530 --> 00:21:24,850
And since I want this group to end, once we get the result, I can just terminate our node, which

294
00:21:24,850 --> 00:21:26,950
will stop this band function within our main function.

295
00:21:27,550 --> 00:21:28,060
All right.

296
00:21:28,060 --> 00:21:29,260
That was a lot to go through.

297
00:21:29,260 --> 00:21:31,750
So let's review what is happening in this code.

298
00:21:32,170 --> 00:21:35,230
We created our navigate Action client node.

299
00:21:35,410 --> 00:21:40,990
And here we created an action client which utilizes our custom navigate interface and listen to the

300
00:21:40,990 --> 00:21:45,970
Navigate action topic name, which matches what we have here in our server.

301
00:21:47,180 --> 00:21:53,630
Then we created a send goal method of our node, which takes in an X, Y, and Z point and configures

302
00:21:53,630 --> 00:21:59,000
our goal weights for the action server to become available and then sends the goal to the action server.

303
00:21:59,180 --> 00:22:04,730
We then set up our future callbacks for our feedback as well as our response.

304
00:22:04,730 --> 00:22:09,620
So when we receive feedback from the action server, we just go ahead and print it to the screen.

305
00:22:09,770 --> 00:22:11,420
Then we have a goal response callback.

306
00:22:11,420 --> 00:22:15,860
So this is the callback which gets returned to us once the action server has decided whether to accept

307
00:22:15,860 --> 00:22:17,470
or reject our goal.

308
00:22:17,480 --> 00:22:24,110
If our goal was accepted, then we added a done callback so that once our action is completed, we can

309
00:22:24,110 --> 00:22:29,360
process the result that gets sent back, which in this case we're just printing the elapsed time in

310
00:22:29,360 --> 00:22:31,520
seconds and shutting down our node.

311
00:22:31,550 --> 00:22:35,990
And since we're trying to do this as asynchronously as possible, we've been utilizing a lot of future

312
00:22:35,990 --> 00:22:38,510
objects which have the result aspect of it.

313
00:22:38,510 --> 00:22:43,580
So whenever we see future result, that just means we're waiting for this future object to get completed

314
00:22:43,580 --> 00:22:44,990
to return us the result.

315
00:22:44,990 --> 00:22:50,840
And only when we call feature result result are we actually working with our result of our particular

316
00:22:50,840 --> 00:22:51,500
action.

317
00:22:52,250 --> 00:22:55,190
So hopefully you have a general understanding of what's happening in this action client node.

318
00:22:55,190 --> 00:23:00,110
So let's go ahead and actually call our send goal function within our main function, which takes in

319
00:23:00,110 --> 00:23:01,880
our x, Y, and Z coordinates.

320
00:23:02,060 --> 00:23:06,980
So I'll go ahead and get rid of this spin one function and instead run our end goal call.

321
00:23:10,130 --> 00:23:15,350
Now we have to actually read in these X, Y, and Z coordinates from the user by adding input calls.

322
00:23:28,880 --> 00:23:33,710
So here we're prompting the user in the terminal to enter an X coordinate, a y coordinate and a Z coordinate,

323
00:23:33,710 --> 00:23:39,320
which we load into these X, y, Z variables, and then we go ahead and send it to our action client

324
00:23:39,320 --> 00:23:41,300
using the send goal function.

325
00:23:41,630 --> 00:23:42,020
All right.

326
00:23:42,020 --> 00:23:44,630
With that, I'll go ahead and save this file.

327
00:23:44,930 --> 00:23:50,840
Oh, and the last thing I forgot to do was spin our node after we send the goal to make sure it's stays

328
00:23:50,840 --> 00:23:54,020
up while our server is processing the action.

329
00:23:56,960 --> 00:23:57,260
All right.

330
00:23:57,260 --> 00:24:00,260
So I did a quick review to make sure I didn't have any errors or typos.

331
00:24:00,260 --> 00:24:05,210
And I actually realized instead of calling the destroying node of our node instance, what I actually

332
00:24:05,210 --> 00:24:07,580
meant to call was RC LP shutdown.

333
00:24:07,580 --> 00:24:13,790
So this will shut down our ROS client libraries communications, which will stop our instance from spinning

334
00:24:13,790 --> 00:24:17,570
our node and we'll shut down this node once you receive our results.

335
00:24:17,570 --> 00:24:20,000
So I'll go ahead and make that change and save the file.

336
00:24:20,030 --> 00:24:22,970
Another typo I noticed was in Navigate action.

337
00:24:22,970 --> 00:24:28,430
I actually misspelled elapsed time, so I swapped the P and the A.

338
00:24:28,430 --> 00:24:31,420
So this is just supposed to be elapsed time like that.

339
00:24:31,430 --> 00:24:34,310
So I'll go ahead and save that as well.

340
00:24:47,310 --> 00:24:47,610
All right.

341
00:24:47,610 --> 00:24:51,000
I'll go ahead and save the file and compile our workspace.

342
00:24:55,920 --> 00:25:01,080
It's gone ahead and compiled so we can go over to our terminal and I'll make sure it's source.

343
00:25:04,140 --> 00:25:06,390
And now we can go ahead and run our action server node.

344
00:25:09,770 --> 00:25:10,760
So our action server is running.

345
00:25:10,760 --> 00:25:14,540
So I'll open a new terminal tab and start up our action client.

346
00:25:14,570 --> 00:25:15,860
Make sure we're sourced.

347
00:25:23,830 --> 00:25:24,190
All right.

348
00:25:24,190 --> 00:25:26,620
So here we get prompted to put in our X, Y, and Z coordinates.

349
00:25:26,620 --> 00:25:31,090
I'll just go ahead and put two for x, three for Y, and I'll just put zero for Z.

350
00:25:31,210 --> 00:25:36,090
So we see we received the goal and it was able to decipher the goal point correctly.

351
00:25:36,100 --> 00:25:40,570
And here we're seeing robot point not detected, and that's because we're not publishing anything to

352
00:25:40,570 --> 00:25:42,290
our robot position topic.

353
00:25:42,310 --> 00:25:44,950
So if I open up a third terminal tab.

354
00:25:46,530 --> 00:25:46,750
We do.

355
00:25:46,770 --> 00:25:48,510
Ross to topic list.

356
00:25:49,110 --> 00:25:55,440
We can see that robot position topic, which was part of the subscriber we created in our server here.

357
00:25:56,370 --> 00:26:00,290
So in order to calculate the distance, we need a position of our current robot.

358
00:26:00,300 --> 00:26:06,150
So if I head back to the terminal, we can use the Ross two topic pub command to manually publish these

359
00:26:06,150 --> 00:26:08,010
values instead of creating a new node.

360
00:26:09,960 --> 00:26:13,380
So this takes in the name of the topic, which in this case is robot position.

361
00:26:13,800 --> 00:26:17,070
And then we have to put in the message type, which is the geometry messages point.

362
00:26:18,640 --> 00:26:22,870
And then we're going to go ahead and put in the value, which I can do by using double quotes and then

363
00:26:22,870 --> 00:26:24,090
a set of curly braces.

364
00:26:24,100 --> 00:26:27,160
And we can set the individual X, Y, and Z attributes.

365
00:26:27,160 --> 00:26:28,720
So I'll just go ahead and put it at the origin.

366
00:26:31,360 --> 00:26:37,270
And I will also use the dash dash one flag so that we're only publishing this value once and it's not

367
00:26:37,270 --> 00:26:40,030
continuously publishing and it'll be printing values to our screen.

368
00:26:40,030 --> 00:26:41,080
So go ahead and run this.

369
00:26:42,660 --> 00:26:43,020
All right.

370
00:26:43,020 --> 00:26:49,380
And now we see we are receiving feedback from our server, which is the given distance from the goal

371
00:26:49,380 --> 00:26:52,010
point to our current position, which is at the origin.

372
00:26:52,020 --> 00:26:55,980
So now I can go ahead and rerun this command and change up some of the values to move us closer to the

373
00:26:55,980 --> 00:26:56,220
point.

374
00:26:56,220 --> 00:26:59,550
For example, if I set X to one and Y to two.

375
00:27:01,160 --> 00:27:06,680
We can see our feedback updates since we got closer to the point and then we can put ourselves close

376
00:27:06,680 --> 00:27:08,600
enough to the point within the distance threshold.

377
00:27:08,600 --> 00:27:18,860
For example, if I do 1.9 and we'll just say 3.05, we are within our 0.1 to 5 meter limit and we get

378
00:27:18,860 --> 00:27:26,630
returned our results back from our action server and we can continue to send multiple new goals back

379
00:27:26,630 --> 00:27:30,050
to our action server so I can set a new one back to the origin.

380
00:27:32,420 --> 00:27:38,480
So we see here we received a new goal at the origin point and I can just go ahead and set us back there.

381
00:27:39,370 --> 00:27:42,370
So we get this feedback of zero and we get our result.

382
00:27:44,460 --> 00:27:44,790
All right.

383
00:27:44,790 --> 00:27:46,020
That was a lot to take in.

384
00:27:46,020 --> 00:27:52,830
But here we have successfully implemented a ROS action in which our client sends a goal point and the

385
00:27:52,830 --> 00:27:59,160
server subscribes to the robot position to calculate the distance which it sends as feedback, and then

386
00:27:59,160 --> 00:28:02,430
finally sends a result of how long it took to complete that action.

387
00:28:03,100 --> 00:28:07,560
In this case, we just manually change the position of the robot to get closer to the point.

388
00:28:07,570 --> 00:28:14,080
But surely you can see how this ROS action framework can be applied in real life, such as commanding

389
00:28:14,080 --> 00:28:18,340
a robotic arm to pick up a particular object and place it somewhere.

390
00:28:18,670 --> 00:28:24,160
In this case, your goal consists of an object for the robot to pick up and the location to place it.

391
00:28:24,310 --> 00:28:29,980
Then within your server command the autonomous code to actually make your robot do the action.

392
00:28:30,190 --> 00:28:35,440
All the while publishing feedback such as the status of the job and eventually receiving a result.

393
00:28:35,440 --> 00:28:38,170
Summary if the job was completed successfully or not.

394
00:28:38,730 --> 00:28:44,400
But nonetheless, congratulations on implementing your very own Ross to action.
