rosros 0.2.5
Simple unified interface to ROS1 / ROS2 Python API
Loading...
Searching...
No Matches
client.py
Go to the documentation of this file.
1"""
2Stand-ins for `rospy.client` in ROS2.
3
4------------------------------------------------------------------------------
5This file is part of rosros - simple unified interface to ROS1 / ROS2.
6Released under the BSD License.
7
8@author Erki Suurjaak
9@created 30.05.2022
10@modified 09.12.2022
11------------------------------------------------------------------------------
12"""
13## @namespace rosros.rospify.client
14import logging
15import re
16import sys
17
18import rclpy.exceptions
19import rclpy.timer
20
21from .. import ros2
22from .. import util
23from . import exceptions
24from . msproxy import MasterProxy
25from . names import get_name, get_namespace
26
27
28# Log levels corresponding to rosgraph_msgs.Log constants
29DEBUG = 1
30INFO = 2
31WARN = 4
32ERROR = 8
33FATAL = 16
34
35
36
37_ROSPY_LOG_LEVEL_TO_PY_LEVEL = {
38 DEBUG: logging.DEBUG,
39 INFO: logging.INFO,
40 WARN: logging.WARN,
41 ERROR: logging.ERROR,
42 FATAL: logging.FATAL,
43}
44
45
46# class to distinguish whether or not user has passed us a default value
47class _unspecified: pass
48
49
50def delete_param(param_name):
51 """
52 Deletes a parameter on the node.
53
54 @param param_name parameter name
55
56 @throws KeyError if parameter is not set
57 """
58 ros2.delete_param(param_name)
59
61def has_param(param_name):
62 """
63 Test if parameter exists on the node.
64
65 @param param_name parameter name
66 """
67 return ros2.has_param(param_name)
68
70def get_master(env=None):
71 """
72 Returns a partial stand-in of `rospy.MasterProxy`.
73
74 @param env ignored (ROS1 compatbility stand-in)
75 """
76 return MasterProxy(ros2.NODE)
77
79def get_param(param_name, default=_unspecified):
80 """
81 Returns a parameter value from the node.
82
83 @param default default value to return if key is not set
84 @return parameter value
85
86 @throws KeyError if value not set and default is not given
87 """
88 if default is _unspecified and not ros2.has_param(param_name):
89 raise KeyError(param_name)
90 return ros2.get_param(param_name, default)
91
92
93
94get_param_cached = get_param
95
97def get_param_names():
98 """Returns a list of node parameter names, with absolute namespace."""
99 ns = util.namejoin(get_namespace(), get_name())
100 return [util.namejoin(ns, x) for x in ros2.get_param_names()]
101
102
103def get_published_topics(namespace="/"):
104 """
105 Returns a list of published topics.
106
107 @return list of topic and type names, as [[topic, type], ]
108
109 @throws ROSException if retrieving topics fails
110 """
111 try:
112 return MasterProxy(ros2.NODE).getPublishedTopics(namespace)[-1]
113 except Exception as e:
114 raise exceptions.ROSException("unable to get published topics") from e
115
116
117def init_node(name, argv=None, anonymous=False, log_level=None,
118 disable_rostime=None, disable_rosout=False,
119 disable_signals=None, xmlrpc_port=None, tcpros_port=None):
120 """
121 Inits ROS2 and creates ROS2 node.
122
123
124 @param name node name, with no namespace
125 @param argv list of command-line arguments for the node
126 @param anonymous whether to auto-generate a unique name for the node,
127 using the given name as base
128 @param log_level log level to set for the node logger,
129 one of `rospify.DEBUG .INFO .WARN .ERROR .FATAL`
130 @param disable_rostime ignored (ROS1 compatibility stand-in)
131 @param disable_rosout whether to suppress auto-publication of rosout
132 @param disable_signals ignored (ROS1 compatibility stand-in)
133 @param xmlrpc_port ignored (ROS1 compatibility stand-in)
134 @param tcpros_port ignored (ROS1 compatibility stand-in)
135
136 @throws ROSInitException if initialization/registration fails
137 @throws ValueError if name is invalid
138 @return `rclpy.node.Node`
139 """
140 if not name:
141 raise ValueError("name must not be empty")
142 if "/" in name:
143 raise ValueError("namespaces are not allowed in node names")
144
145 log_level = _ROSPY_LOG_LEVEL_TO_PY_LEVEL.get(log_level, log_level)
146 try: return ros2.init_node(name, argv, anonymous=anonymous, log_level=log_level,
147 enable_rosout=not disable_rosout)
148 except Exception as e:
149 raise exceptions.ROSInitException() from e
150
151
152def myargv(argv=None):
153 """
154 Returns command-line arguments with remappings removed.
155
156 @param argv arguments to use if not `sys.argv`
157 @return list of arguments with remappings removed
158 """
159 REMAP_RGX = re.compile(r"^([\~\/a-z]|_|__)[\w\/]*:=.*", re.I)
160 argv = list(sys.argv if argv is None else argv)
161 while "--remap" in argv:
162 idx = argv.index("--remap")
163 argv.pop(idx)
164 while len(argv) > idx and REMAP_RGX.match(argv[idx]):
165 argv.pop(idx)
166 return argv
167
168
169def on_shutdown(h):
170 """Registers function to be called on shutdown, after node has been torn down."""
171 ros2.on_shutdown(h)
172
173
174def search_param(param_name):
175 """
176 Search for a parameter on the node.
177
178 @param param_name parameter name
179 @return key of matching parameter or None if no matching parameter
180 """
181 ns = util.namejoin(get_namespace(), get_name())
182 if param_name.startswith(ns):
183 param_name = param_name[len(ns):]
184 return util.namejoin(ns, param_name) if param_name in ros2.get_param_names() else None
185
186
187def set_param(param_name, param_value):
188 """
189 Set a parameter on the node.
190
191 If param_value is a dictionary, it will be treated as a parameter tree,
192 where param_name is the namespace. For example:::
193 {"x": 1, "y": 2, "sub": {"z": 3}}
194 will set param_name.x=1, param_name.y=2, and param_name.sub.z=3.
195 Furthermore, it will replace all existing parameters in the
196 param_name namespace with the parameters in param_value. You must
197 set parameters individually if you wish to perform a union update.
198
199 @param param_name parameter name
200 @param param_value parameter value
201
202 @throws ROSException if setting parameter failed
203 """
204 try:
205 if isinstance(param_value, dict):
206 ns = "%s." % param_name.strip(".")
207 for n in [x for x in ros2.get_param_names() if x.startswith(ns)]:
208 ros2.delete_param(n)
209 for k, v in util.flatten_dict({param_name: param_value}).items():
210 ros2.set_param(k, v)
211 else: ros2.set_param(param_name, param_value)
212 except Exception as e:
213 raise exceptions.ROSException() from e
214
215
216def spin():
217 """Spins ROS2 node forever."""
218 ros2.spin()
219
220
221def wait_for_message(topic, topic_type, timeout=None):
222 """
223 Receive one message from topic.
224
225 This will create a new subscription to the topic, receive one message, then unsubscribe.
226
227 @param topic name of topic
228 @param topic_type topic type class
229 @param timeout timeout time in seconds or ROS Duration
230 @return ROS message
231
232 @throws ROSException if specified timeout is exceeded
233 @throws ROSInterruptException if shutdown interrupts wait
234 """
235 timeout = ros2.to_sec(timeout) if timeout is not None else 2**31 - 1
236 sub, timer, rate = None, None, None
237
238 try:
239 def on_message(msg):
240 msgs.append(msg)
241 ros2.destroy_entity(rate)
242
243 msgs = []
244 timer = ros2.create_timer(timeout, callback=None)
245 rate = rclpy.timer.Rate(timer, context=ros2.NODE.context)
246 sub = ros2.create_subscriber(topic, topic_type, on_message)
247 if not msgs: rate.sleep()
248 if not msgs:
249 err = "timeout exceeded while waiting for message on topic %s" % topic
250 raise exceptions.ROSException(err)
251 except rclpy.exceptions.ROSInterruptException as e:
252 raise exceptions.ROSInterruptException(*e.args) from e
253 finally:
254 ros2.destroy_entity(timer)
255 ros2.destroy_entity(sub)
256
257
258__all__ = [
259 "delete_param", "get_master", "get_param", "get_param_cached", "get_param_names",
260 "get_published_topics", "has_param", "init_node", "myargv", "on_shutdown",
261 "search_param", "set_param", "spin", "wait_for_message"
262]
Base exception class for ROS clients.
Definition exceptions.py:54
Exception for operations that interrupted, e.g.
Definition exceptions.py:60
Exception if time moved backwards.
Definition exceptions.py:69
Partial stand-in for `rospy.MasterProxy`.
Definition msproxy.py:18
spin()
Spins ROS2 node forever.
Definition client.py:218
get_param_names()
Returns a list of node parameter names, with absolute namespace.
Definition client.py:99