Skip to content

Commit 1d1cd6b

Browse files
committed
Add actions to ros loader
1 parent 910163b commit 1d1cd6b

File tree

3 files changed

+185
-42
lines changed

3 files changed

+185
-42
lines changed

rosbridge_library/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
<test_depend>builtin_interfaces</test_depend>
3636
<test_depend>diagnostic_msgs</test_depend>
3737
<test_depend>example_interfaces</test_depend>
38+
<test_depend>control_msgs</test_depend>
3839
<test_depend>geometry_msgs</test_depend>
3940
<test_depend>nav_msgs</test_depend>
4041
<test_depend>sensor_msgs</test_depend>

rosbridge_library/src/rosbridge_library/internal/ros_loader.py

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,10 @@
4444
# Variable containing the loaded classes
4545
_loaded_msgs = {}
4646
_loaded_srvs = {}
47+
_loaded_actions = {}
4748
_msgs_lock = Lock()
4849
_srvs_lock = Lock()
50+
_actions_lock = Lock()
4951

5052

5153
class InvalidTypeStringException(Exception):
@@ -75,14 +77,20 @@ def get_message_class(typestring):
7577
"""Loads the message type specified.
7678
7779
Returns the loaded class, or throws exceptions on failure"""
78-
return _get_msg_class(typestring)
80+
return _get_interface_class(typestring, "msg", _loaded_msgs, _msgs_lock)
7981

8082

8183
def get_service_class(typestring):
8284
"""Loads the service type specified.
8385
8486
Returns the loaded class, or None on failure"""
85-
return _get_srv_class(typestring)
87+
return _get_interface_class(typestring, "srv", _loaded_srvs, _srvs_lock)
88+
89+
90+
def get_action_class(typestring):
91+
"""Loads the action type specified.
92+
Returns the loaded class, or throws exceptions on failure"""
93+
return _get_interface_class(typestring, "action", _loaded_actions, _actions_lock)
8694

8795

8896
def get_message_instance(typestring):
@@ -102,48 +110,41 @@ def get_service_response_instance(typestring):
102110
return cls.Response()
103111

104112

105-
def _get_msg_class(typestring):
106-
"""If not loaded, loads the specified msg class then returns an instance
107-
of it
113+
def get_action_goal_instance(typestring):
114+
cls = get_action_class(typestring)
115+
return cls.Goal()
108116

109-
Throws various exceptions if loading the msg class fails"""
110-
global _loaded_msgs, _msgs_lock
111-
try:
112-
# The type string starts with the package and ends with the
113-
# class and contains module subnames in between. For
114-
# compatibility with ROS1 style types, we fall back to use a
115-
# standard "msg" subname.
116-
splits = [x for x in typestring.split("/") if x]
117-
if len(splits) > 2:
118-
subname = ".".join(splits[1:-1])
119-
else:
120-
subname = "msg"
121117

122-
return _get_class(typestring, subname, _loaded_msgs, _msgs_lock)
123-
except (InvalidModuleException, InvalidClassException):
124-
return _get_class(typestring, "msg", _loaded_msgs, _msgs_lock)
118+
def get_action_feedback_instance(typestring):
119+
cls = get_action_class(typestring)
120+
return cls.Feedback()
121+
125122

123+
def get_action_result_instance(typestring):
124+
cls = get_action_class(typestring)
125+
return cls.Result()
126126

127-
def _get_srv_class(typestring):
128-
"""If not loaded, loads the specified srv class then returns an instance
129-
of it
130127

131-
Throws various exceptions if loading the srv class fails"""
132-
global _loaded_srvs, _srvs_lock
128+
def _get_interface_class(typestring, intf_type, loaded_intfs, intf_lock):
129+
"""
130+
If not loaded, loads the specified ROS interface class then returns an instance of it.
131+
132+
Throws various exceptions if loading the interface class fails.
133+
"""
133134
try:
134135
# The type string starts with the package and ends with the
135136
# class and contains module subnames in between. For
136-
# compatibility with ROS1 style types, we fall back to use a
137-
# standard "srv" subname.
137+
# compatibility with ROS 1 style types, we fall back to use a
138+
# standard "msg" subname.
138139
splits = [x for x in typestring.split("/") if x]
139140
if len(splits) > 2:
140141
subname = ".".join(splits[1:-1])
141142
else:
142-
subname = "srv"
143+
subname = intf_type
143144

144-
return _get_class(typestring, subname, _loaded_srvs, _srvs_lock)
145+
return _get_class(typestring, subname, loaded_intfs, intf_lock)
145146
except (InvalidModuleException, InvalidClassException):
146-
return _get_class(typestring, "srv", _loaded_srvs, _srvs_lock)
147+
return _get_class(typestring, intf_type, loaded_intfs, intf_lock)
147148

148149

149150
def _get_class(typestring, subname, cache, lock):

rosbridge_library/test/internal/test_ros_loader.py

Lines changed: 153 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66

77

88
class TestROSLoader(unittest.TestCase):
9-
def test_bad_msgnames(self):
9+
#################
10+
# Message Tests #
11+
#################
12+
def test_bad_msg_names(self):
1013
bad = [
1114
"",
1215
"/",
@@ -33,7 +36,7 @@ def test_bad_msgnames(self):
3336
ros_loader.InvalidTypeStringException, ros_loader.get_message_instance, x
3437
)
3538

36-
def test_irregular_msgnames(self):
39+
def test_irregular_msg_names(self):
3740
irregular = [
3841
"std_msgs//String",
3942
"//std_msgs/String",
@@ -49,7 +52,7 @@ def test_irregular_msgnames(self):
4952
self.assertNotEqual(ros_loader.get_message_class(x), None)
5053
self.assertNotEqual(ros_loader.get_message_instance(x), None)
5154

52-
def test_std_msgnames(self):
55+
def test_std_msg_names(self):
5356
stdmsgs = [
5457
"std_msgs/Bool",
5558
"std_msgs/Byte",
@@ -124,7 +127,7 @@ def test_msg_cache(self):
124127
self.assertEqual(get_message(x), type(inst))
125128
self.assertTrue(x in ros_loader._loaded_msgs)
126129

127-
def test_assorted_msgnames(self):
130+
def test_assorted_msg_names(self):
128131
assortedmsgs = [
129132
"geometry_msgs/Pose",
130133
"actionlib_msgs/GoalStatus",
@@ -145,7 +148,7 @@ def test_assorted_msgnames(self):
145148
self.assertNotEqual(inst, None)
146149
self.assertEqual(get_message(x), type(inst))
147150

148-
def test_invalid_msgnames_primitives(self):
151+
def test_invalid_msg_names_primitives(self):
149152
invalid = [
150153
"bool",
151154
"int8",
@@ -172,7 +175,7 @@ def test_invalid_msgnames_primitives(self):
172175
x,
173176
)
174177

175-
def test_nonexistent_packagenames(self):
178+
def test_nonexistent_package_names(self):
176179
nonexistent = [
177180
"wangle_msgs/Jam",
178181
"whistleblower_msgs/Document",
@@ -195,7 +198,7 @@ def test_packages_without_msgs(self):
195198
self.assertRaises(ros_loader.InvalidModuleException, ros_loader.get_message_class, x)
196199
self.assertRaises(ros_loader.InvalidModuleException, ros_loader.get_message_instance, x)
197200

198-
def test_nonexistent_msg_classnames(self):
201+
def test_nonexistent_msg_class_names(self):
199202
nonexistent = [
200203
"rcl_interfaces/Time",
201204
"rcl_interfaces/Duration",
@@ -208,7 +211,10 @@ def test_nonexistent_msg_classnames(self):
208211
self.assertRaises(ros_loader.InvalidClassException, ros_loader.get_message_class, x)
209212
self.assertRaises(ros_loader.InvalidClassException, ros_loader.get_message_instance, x)
210213

211-
def test_bad_servicenames(self):
214+
#################
215+
# Service Tests #
216+
#################
217+
def test_bad_service_names(self):
212218
bad = [
213219
"",
214220
"/",
@@ -241,7 +247,7 @@ def test_bad_servicenames(self):
241247
ros_loader.InvalidTypeStringException, ros_loader.get_service_response_instance, x
242248
)
243249

244-
def test_irregular_servicenames(self):
250+
def test_irregular_service_names(self):
245251
irregular = [
246252
"rcl_interfaces//GetParameters",
247253
"/rcl_interfaces/GetParameters/",
@@ -258,7 +264,7 @@ def test_irregular_servicenames(self):
258264
self.assertNotEqual(ros_loader.get_service_request_instance(x), None)
259265
self.assertNotEqual(ros_loader.get_service_response_instance(x), None)
260266

261-
def test_common_servicenames(self):
267+
def test_common_service_names(self):
262268
common = [
263269
"rcl_interfaces/GetParameters",
264270
"rcl_interfaces/SetParameters",
@@ -302,7 +308,7 @@ def test_packages_without_srvs(self):
302308
ros_loader.InvalidModuleException, ros_loader.get_service_response_instance, x
303309
)
304310

305-
def test_nonexistent_service_packagenames(self):
311+
def test_nonexistent_service_package_names(self):
306312
nonexistent = [
307313
"butler_srvs/FetchDrink",
308314
"money_srvs/MoreMoney",
@@ -318,7 +324,7 @@ def test_nonexistent_service_packagenames(self):
318324
ros_loader.InvalidModuleException, ros_loader.get_service_response_instance, x
319325
)
320326

321-
def test_nonexistent_service_classnames(self):
327+
def test_nonexistent_service_class_names(self):
322328
nonexistent = [
323329
"std_srvs/Reboot",
324330
"std_srvs/Full",
@@ -332,3 +338,138 @@ def test_nonexistent_service_classnames(self):
332338
self.assertRaises(
333339
ros_loader.InvalidClassException, ros_loader.get_service_response_instance, x
334340
)
341+
342+
################
343+
# Action Tests #
344+
################
345+
def test_bad_action_names(self):
346+
bad = [
347+
"",
348+
"/",
349+
"//",
350+
"///",
351+
"////",
352+
"/////",
353+
"bad",
354+
"stillbad",
355+
"not/better/even/still",
356+
"not//better//even//still",
357+
"not///better///even///still",
358+
"better/",
359+
"better//",
360+
"better///",
361+
"/better",
362+
"//better",
363+
"///better",
364+
r"this\isbad",
365+
"\\",
366+
]
367+
for x in bad:
368+
self.assertRaises(ros_loader.InvalidTypeStringException, ros_loader.get_action_class, x)
369+
self.assertRaises(
370+
ros_loader.InvalidTypeStringException, ros_loader.get_action_goal_instance, x
371+
)
372+
self.assertRaises(
373+
ros_loader.InvalidTypeStringException, ros_loader.get_action_feedback_instance, x
374+
)
375+
self.assertRaises(
376+
ros_loader.InvalidTypeStringException, ros_loader.get_action_result_instance, x
377+
)
378+
379+
def test_irregular_action_names(self):
380+
irregular = [
381+
"example_interfaces//Fibonacci",
382+
"/example_interfaces/Fibonacci/",
383+
"/example_interfaces/Fibonacci",
384+
"//example_interfaces/Fibonacci",
385+
"/example_interfaces//Fibonacci",
386+
"example_interfaces/Fibonacci//",
387+
"/example_interfaces/Fibonacci//",
388+
"example_interfaces/Fibonacci/",
389+
"example_interfaces//Fibonacci//",
390+
]
391+
for x in irregular:
392+
self.assertNotEqual(ros_loader.get_action_class(x), None)
393+
self.assertNotEqual(ros_loader.get_action_goal_instance(x), None)
394+
self.assertNotEqual(ros_loader.get_action_feedback_instance(x), None)
395+
self.assertNotEqual(ros_loader.get_action_result_instance(x), None)
396+
397+
def test_common_action_names(self):
398+
common = [
399+
"control_msgs/FollowJointTrajectory",
400+
"tf2_msgs/LookupTransform",
401+
"example_interfaces/Fibonacci",
402+
]
403+
for x in common:
404+
self.assertNotEqual(ros_loader.get_action_class(x), None)
405+
self.assertNotEqual(ros_loader.get_action_goal_instance(x), None)
406+
self.assertNotEqual(ros_loader.get_action_feedback_instance(x), None)
407+
self.assertNotEqual(ros_loader.get_action_result_instance(x), None)
408+
409+
def test_action_cache(self):
410+
common = [
411+
"control_msgs/FollowJointTrajectory",
412+
"tf2_msgs/LookupTransform",
413+
"example_interfaces/Fibonacci",
414+
]
415+
for x in common:
416+
self.assertNotEqual(ros_loader.get_action_class(x), None)
417+
self.assertNotEqual(ros_loader.get_action_goal_instance(x), None)
418+
self.assertNotEqual(ros_loader.get_action_feedback_instance(x), None)
419+
self.assertNotEqual(ros_loader.get_action_result_instance(x), None)
420+
self.assertTrue(x in ros_loader._loaded_actions)
421+
422+
def test_packages_without_actions(self):
423+
no_msgs = ["roslib/A", "roslib/B", "roslib/C", "std_msgs/CuriousSrv"]
424+
for x in no_msgs:
425+
self.assertRaises(ros_loader.InvalidModuleException, ros_loader.get_action_class, x)
426+
self.assertRaises(
427+
ros_loader.InvalidModuleException, ros_loader.get_action_goal_instance, x
428+
)
429+
self.assertRaises(
430+
ros_loader.InvalidModuleException, ros_loader.get_action_feedback_instance, x
431+
)
432+
self.assertRaises(
433+
ros_loader.InvalidModuleException, ros_loader.get_action_result_instance, x
434+
)
435+
436+
def test_nonexistent_action_package_names(self):
437+
nonexistent = [
438+
"butler_srvs/SetTable",
439+
"money_srvs/WithdrawMoreMoney",
440+
"snoopdogg_actions/LayBackWithMyMindOnMyMoneyAndMyMoneyOnMyMind",
441+
"revenge_actions/PlotRevenge",
442+
]
443+
for x in nonexistent:
444+
self.assertRaises(ros_loader.InvalidModuleException, ros_loader.get_action_class, x)
445+
self.assertRaises(
446+
ros_loader.InvalidModuleException, ros_loader.get_action_goal_instance, x
447+
)
448+
self.assertRaises(
449+
ros_loader.InvalidModuleException, ros_loader.get_action_feedback_instance, x
450+
)
451+
self.assertRaises(
452+
ros_loader.InvalidModuleException, ros_loader.get_action_result_instance, x
453+
)
454+
455+
def test_nonexistent_action_class_names(self):
456+
nonexistent = [
457+
"control_msgs/ControlFusionReactor",
458+
"tf2_msgs/GetDualQuaternionRepresentation",
459+
"example_interfaces/DoNonexistentAction",
460+
]
461+
for x in nonexistent:
462+
self.assertRaises(ros_loader.InvalidClassException, ros_loader.get_action_class, x)
463+
self.assertRaises(
464+
ros_loader.InvalidClassException, ros_loader.get_action_goal_instance, x
465+
)
466+
self.assertRaises(
467+
ros_loader.InvalidClassException, ros_loader.get_action_feedback_instance, x
468+
)
469+
self.assertRaises(
470+
ros_loader.InvalidClassException, ros_loader.get_action_result_instance, x
471+
)
472+
473+
474+
if __name__ == "__main__":
475+
unittest.main()

0 commit comments

Comments
 (0)