18 from smach_msgs.msg
import SmachContainerStatus
19 from smach_msgs.msg
import SmachContainerStructure
24 This class represents a given container in a running SMACH system.
26 Its primary use is to generate dotcode for a SMACH container. It has
27 methods for responding to structure and status messages from a SMACH
28 introspection server, as well as methods for updating the styles of a
29 graph once it's been drawn.
35 splitpath = msg.path.split(
'/')
37 self.
_dir =
'/'.join(splitpath[0:-1])
54 """Update the structure of this container from a given message. Return True if anything changes."""
75 """Unpack the user data"""
76 if re.match(
r'^[A-Za-z0-9+/]*={0,2}$', msg.local_data):
77 local_data_raw_bytes = base64.b64decode(msg.local_data)
78 local_data = pickle.loads(local_data_raw_bytes)
79 elif isinstance(msg.local_data, str)
and sys.version_info.major >= 3:
80 local_data = pickle.loads(msg.local_data.encode(
'utf-8'))
82 local_data = pickle.loads(msg.local_data)
86 """Update the known userdata and active state set and return True if the graph needs to be redrawn."""
104 while not rospy.is_shutdown():
108 except ImportError
as ie:
110 modulename = ie.args[0][16:]
111 packagename = modulename[0:modulename.find(
'.')]
112 roslib.load_manifest(packagename)
116 self.
_info = msg.info
120 def get_dotcode(self, selected_paths, closed_paths, depth, max_depth, containers, show_all, label_wrapper, attrs={}):
121 """Generate the dotcode representing this container.
123 @param selected_paths: The paths to nodes that are selected
124 @closed paths: The paths that shouldn't be expanded
125 @param depth: The depth to start traversing the tree
126 @param max_depth: The depth to which we should traverse the tree
127 @param containers: A dict of containers keyed by their paths
128 @param show_all: True if implicit transitions should be shown
129 @param label_wrapper: A text wrapper for wrapping element names
130 @param attrs: A dict of dotcode attributes for this cluster
133 dotstr =
'subgraph "cluster_%s" {\n' % (self.
_path)
136 attrs[
'color'] =
'#00000000'
137 attrs[
'fillcolor'] =
'#0000000F'
152 proxy_attrs[
'label'] =
'\\n'.join(label_wrapper.wrap(self.
_label))
153 dotstr +=
'"%s" %s;\n' % (
154 '/'.join([self.
_path,
'__proxy__']),
158 if max_depth == -1
or depth <= max_depth:
160 dotstr +=
'subgraph "cluster_%s" {\n' %
'/'.join([self.
_path,
'__outcomes__'])
162 'style':
'rounded,filled',
165 'fillcolor':
'#FFFFFF00'
170 outcome_path =
':'.join([self.
_path,outcome_label])
174 'style':
'filled,rounded',
176 'fillcolor':
'#FE464f',
178 'fontcolor':
'#780006',
180 'xlabel':
'\\n'.join(label_wrapper.wrap(outcome_label)),
181 'URL':
':'.join([self.
_path,outcome_label])
183 dotstr +=
'"%s" %s;\n' % (outcome_path,
attr_string(outcome_attrs))
188 child_path =
'/'.join([self.
_path,child_label])
190 if child_path
in containers:
192 child_color =
'#5C7600FF'
193 child_fillcolor =
'#C0F700FF'
196 child_color =
'#000000FF'
197 child_fillcolor =
'gray'
201 'style':
'filled,setlinewidth({}),rounded'.format(child_linewidth),
202 'color': child_color,
203 'fillcolor': child_fillcolor,
217 child_color =
'#5C7600FF'
218 child_fillcolor =
'#C0F700FF'
221 child_color =
'#000000FF'
222 child_fillcolor =
'#FFFFFFFF'
226 'style':
'filled,setlinewidth({})'.format(child_linewidth),
227 'color': child_color,
228 'fillcolor': child_fillcolor,
229 'label':
'\\n'.join(label_wrapper.wrap(child_label)),
232 dotstr +=
'"%s" %s;\n' % (child_path,
attr_string(child_attrs))
235 internal_edges = list(zip(
241 internal_edges += [(
'',
'__proxy__',initial_child)
for initial_child
in self.
_initial_states]
243 has_explicit_transitions = []
244 for (outcome_label,from_label,to_label)
in internal_edges:
245 if to_label !=
'None' or outcome_label == to_label:
246 has_explicit_transitions.append(from_label)
249 for (outcome_label,from_label,to_label)
in internal_edges:
251 from_path =
'/'.join([self.
_path, from_label])
254 or to_label !=
'None'\
255 or from_label
not in has_explicit_transitions \
256 or (outcome_label == from_label) \
257 or from_path
in containers:
259 if to_label ==
'None':
260 to_label = outcome_label
262 to_path =
'/'.join([self.
_path, to_label])
265 'URL':
':'.join([from_path,outcome_label,to_path]),
268 'xlabel':
'\\n'.join(label_wrapper.wrap(outcome_label))}
269 edge_attrs[
'style'] =
'setlinewidth(2)'
275 from_key =
'"%s"' % from_path
276 if from_path
in containers:
277 if max_depth == -1
or depth+1 <= max_depth:
278 from_key =
'"%s:%s"' % ( from_path, outcome_label)
280 edge_attrs[
'ltail'] =
'cluster_'+from_path
281 from_path =
'/'.join([from_path,
'__proxy__'])
282 from_key =
'"%s"' % ( from_path )
286 to_key =
'"%s:%s"' % (self.
_path,to_label)
287 edge_attrs[
'color'] =
'#00000055'
289 if to_path
in containers:
290 edge_attrs[
'lhead'] =
'cluster_'+to_path
291 to_path =
'/'.join([to_path,
'__proxy__'])
292 to_key =
'"%s"' % to_path
294 dotstr +=
'%s -> %s %s;\n' % (
303 _container_class = ContainerNode
314 self.
_client = smach_ros.IntrospectionClient()
339 """This thread continuously updates the graph when it changes.
341 The graph gets updated in one of two ways:
343 1: The structure of the SMACH plans has changed, or the display
344 settings have been changed. In this case, the dotcode needs to be
347 2: The status of the SMACH plans has changed. In this case, we only
348 need to change the styles of the graph.
358 containers_to_update = {}
361 containers_to_update = {
363 elif self.
_path ==
'/':
371 dotstr =
"digraph {\n\t"
374 "outputmode=nodesfirst",
390 for path, tc
in containers_to_update.items():
391 dotstr += tc.get_dotcode(
397 if len(containers_to_update) == 0:
398 dotstr +=
'"__empty__" ' \
399 +
'[label="Path not available.", shape="plaintext"]'
403 return containers_to_update
406 """Update the list of known SMACH introspection servers."""
409 server_names = self.
_client.get_servers()
411 sn
for sn
in server_names
if sn
not in self.
_status_subs]
414 for server_name
in new_server_names:
416 server_name+smach_ros.introspection.STRUCTURE_TOPIC,
417 SmachContainerStructure,
419 callback_args=server_name,
423 server_name+smach_ros.introspection.STATUS_TOPIC,
424 SmachContainerStatus,
432 """Update the structure of the SMACH plan (re-generate the dotcode)."""
440 pathsplit = path.split(
'/')
441 parent_path =
'/'.join(pathsplit[0:-1])
443 rospy.logdebug(
"RECEIVED: "+path)
444 rospy.logdebug(
"CONTAINERS: "+str(list(self.
_containers.keys())))
450 rospy.logdebug(
"UPDATING: "+path)
453 needs_redraw = self.
_containers[path].update_structure(msg)
455 rospy.logdebug(
"CONSTRUCTING: "+path)
462 if parent_path ==
'':
479 """Process status messages."""
487 rospy.logdebug(
"STATUS MSG: "+path)
494 if container.update_status(msg):