ros_topics.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 
00003 # Thanks to:
00004 # https://github.com/ros-visualization/rqt_common_plugins/blob/groovy-devel/rqt_topic/src/rqt_topic/topic_widget.py
00005 import numpy
00006 import random
00007 
00008 import roslib
00009 import roslib.message
00010 import rospy
00011 from opcua import ua, uamethod
00012 
00013 import ros_actions
00014 import ros_server
00015 import rostopic
00016 
00017 
00018 class OpcUaROSTopic:
00019     def __init__(self, server, parent, idx, topic_name, topic_type):
00020         self.server = server
00021         self.parent = self.recursive_create_objects(topic_name, idx, parent)
00022         self.type_name = topic_type
00023         self.name = topic_name
00024         self._nodes = {}
00025         self.idx = idx
00026 
00027         self.message_class = None
00028         try:
00029             self.message_class = roslib.message.get_message_class(topic_type)
00030             self.message_instance = self.message_class()
00031 
00032         except rospy.ROSException:
00033             self.message_class = None
00034             rospy.logfatal("Couldn't find message class for type " + topic_type)
00035 
00036         self._recursive_create_items(self.parent, idx, topic_name, topic_type, self.message_instance, True)
00037 
00038         self._subscriber = rospy.Subscriber(self.name, self.message_class, self.message_callback)
00039         self._publisher = rospy.Publisher(self.name, self.message_class, queue_size=1)
00040         rospy.loginfo("Created ROS Topic with name: " + str(self.name))
00041 
00042     def _recursive_create_items(self, parent, idx, topic_name, type_name, message, top_level=False):
00043         topic_text = topic_name.split('/')[-1]
00044         if '[' in topic_text:
00045             topic_text = topic_text[topic_text.index('['):]
00046 
00047         # This here are 'complex datatypes'
00048         if hasattr(message, '__slots__') and hasattr(message, '_slot_types'):
00049             complex_type = True
00050             new_node = parent.add_object(ua.NodeId(topic_name, parent.nodeid.NamespaceIndex, ua.NodeIdType.String),
00051                                          ua.QualifiedName(topic_name, parent.nodeid.NamespaceIndex))
00052             new_node.add_property(ua.NodeId(topic_name + ".Type", idx),
00053                                   ua.QualifiedName("Type", parent.nodeid.NamespaceIndex), type_name)
00054             if top_level:
00055                 new_node.add_method(ua.NodeId(topic_name + ".Update", parent.nodeid.NamespaceIndex),
00056                                     ua.QualifiedName("Update", parent.nodeid.NamespaceIndex),
00057                                     self.opcua_update_callback, [], [])
00058             for slot_name, type_name_child in zip(message.__slots__, message._slot_types):
00059                 self._recursive_create_items(new_node, idx, topic_name + '/' + slot_name, type_name_child,
00060                                              getattr(message, slot_name))
00061             self._nodes[topic_name] = new_node
00062 
00063         else:
00064             # This are arrays
00065             base_type_str, array_size = _extract_array_info(type_name)
00066             try:
00067                 base_instance = roslib.message.get_message_class(base_type_str)()
00068             except (ValueError, TypeError):
00069                 base_instance = None
00070 
00071             if array_size is not None and hasattr(base_instance, '__slots__'):
00072                 for index in range(array_size):
00073                     self._recursive_create_items(parent, idx, topic_name + '[%d]' % index, base_type_str, base_instance)
00074             else:
00075                 new_node = _create_node_with_type(parent, idx, topic_name, topic_text, type_name, array_size)
00076                 self._nodes[topic_name] = new_node
00077 
00078         if topic_name in self._nodes and self._nodes[topic_name].get_node_class() == ua.NodeClass.Variable:
00079             self._nodes[topic_name].set_writable(True)
00080         return
00081 
00082     def message_callback(self, message):
00083         self.update_value(self.name, message)
00084 
00085     @uamethod
00086     def opcua_update_callback(self, parent):
00087         try:
00088             for nodeName in self._nodes:
00089                 child = self._nodes[nodeName]
00090                 name = child.get_display_name().Text
00091                 if hasattr(self.message_instance, name):
00092                     if child.get_node_class() == ua.NodeClass.Variable:
00093                         setattr(self.message_instance, name,
00094                                 correct_type(child, type(getattr(self.message_instance, name))))
00095                     elif child.get_node_class == ua.NodeClass.Object:
00096                         setattr(self.message_instance, name, self.create_message_instance(child))
00097             self._publisher.publish(self.message_instance)
00098         except rospy.ROSException as e:
00099             rospy.logerr("Error when updating node " + self.name, e)
00100             self.server.server.delete_nodes([self.parent])
00101 
00102     def update_value(self, topic_name, message):
00103         if hasattr(message, '__slots__') and hasattr(message, '_slot_types'):
00104             for slot_name in message.__slots__:
00105                 self.update_value(topic_name + '/' + slot_name, getattr(message, slot_name))
00106 
00107         elif type(message) in (list, tuple):
00108             if (len(message) > 0) and hasattr(message[0], '__slots__'):
00109                 for index, slot in enumerate(message):
00110                     if topic_name + '[%d]' % index in self._nodes:
00111                         self.update_value(topic_name + '[%d]' % index, slot)
00112                     else:
00113                         if topic_name in self._nodes:
00114                             base_type_str, _ = _extract_array_info(
00115                                 self._nodes[topic_name].text(self.type_name))
00116                             self._recursive_create_items(self._nodes[topic_name], topic_name + '[%d]' % index,
00117                                                          base_type_str,
00118                                                          slot, None)
00119             # remove obsolete children
00120             if topic_name in self._nodes:
00121                 if len(message) < len(self._nodes[topic_name].get_children()):
00122                     for i in range(len(message), self._nodes[topic_name].childCount()):
00123                         item_topic_name = topic_name + '[%d]' % i
00124                         self.recursive_delete_items(self._nodes[item_topic_name])
00125                         del self._nodes[item_topic_name]
00126         else:
00127             if topic_name in self._nodes and self._nodes[topic_name] is not None:
00128                 self._nodes[topic_name].set_value(repr(message))
00129 
00130     def recursive_delete_items(self, item):
00131         self._publisher.unregister()
00132         self._subscriber.unregister()
00133         for child in item.get_children():
00134             self.recursive_delete_items(child)
00135             if child in self._nodes:
00136                 del self._nodes[child]
00137             self.server.server.delete_nodes([child])
00138         self.server.server.delete_nodes([item])
00139         if len(self.parent.get_children()) == 0:
00140             self.server.server.delete_nodes([self.parent])
00141 
00142     def create_message_instance(self, node):
00143         for child in node.get_children():
00144             name = child.get_display_name().Text
00145             if hasattr(self.message_instance, name):
00146                 if child.get_node_class() == ua.NodeClass.Variable:
00147                     setattr(self.message_instance, name,
00148                             correct_type(child, type(getattr(self.message_instance, name))))
00149                 elif child.get_node_class == ua.NodeClass.Object:
00150                     setattr(self.message_instance, name, self.create_message_instance(child))
00151         return self.message_instance  # Converts the value of the node to that specified in the ros message we are trying to fill. Casts python ints
00152 
00153     def recursive_create_objects(self, topic_name, idx, parent):
00154         hierachy = topic_name.split('/')
00155         if len(hierachy) == 0 or len(hierachy) == 1:
00156             return parent
00157         for name in hierachy:
00158             if name != '':
00159                 try:
00160                     nodewithsamename = self.server.find_topics_node_with_same_name(name, idx)
00161                     if nodewithsamename is not None:
00162                         return self.recursive_create_objects(ros_server.nextname(hierachy, hierachy.index(name)), idx,
00163                                                              nodewithsamename)
00164                     else:
00165                         # if for some reason 2 services with exactly same name are created use hack>: add random int, prob to hit two
00166                         # same ints 1/10000, should be sufficient
00167                         newparent = parent.add_object(
00168                             ua.NodeId(name, parent.nodeid.NamespaceIndex, ua.NodeIdType.String),
00169                             ua.QualifiedName(name, parent.nodeid.NamespaceIndex))
00170                         return self.recursive_create_objects(ros_server.nextname(hierachy, hierachy.index(name)), idx,
00171                                                              newparent)
00172                 # thrown when node with parent name is not existent in server
00173                 except IndexError, common.UaError:
00174                     newparent = parent.add_object(
00175                         ua.NodeId(name + str(random.randint(0, 10000)), parent.nodeid.NamespaceIndex,
00176                                   ua.NodeIdType.String),
00177                         ua.QualifiedName(name, parent.nodeid.NamespaceIndex))
00178                     return self.recursive_create_objects(ros_server.nextname(hierachy, hierachy.index(name)), idx,
00179                                                          newparent)
00180 
00181         return parent
00182 
00183 
00184 # to unsigned integers as to fulfill ros specification. Currently only uses a few different types,
00185 # no other types encountered so far.
00186 def correct_type(node, typemessage):
00187     data_value = node.get_data_value()
00188     result = node.get_value()
00189     if isinstance(data_value, ua.DataValue):
00190         if typemessage.__name__ == "float":
00191             result = numpy.float(result)
00192         if typemessage.__name__ == "double":
00193             result = numpy.double(result)
00194         if typemessage.__name__ == "int":
00195             result = int(result) & 0xff
00196     else:
00197         rospy.logerr("can't convert: " + str(node.get_data_value.Value))
00198         return None
00199     return result
00200 
00201 
00202 def _extract_array_info(type_str):
00203     array_size = None
00204     if '[' in type_str and type_str[-1] == ']':
00205         type_str, array_size_str = type_str.split('[', 1)
00206         array_size_str = array_size_str[:-1]
00207         if len(array_size_str) > 0:
00208             array_size = int(array_size_str)
00209         else:
00210             array_size = 0
00211 
00212     return type_str, array_size
00213 
00214 
00215 def _create_node_with_type(parent, idx, topic_name, topic_text, type_name, array_size):
00216     if '[' in type_name:
00217         type_name = type_name[:type_name.index('[')]
00218 
00219     if type_name == 'bool':
00220         dv = ua.Variant(False, ua.VariantType.Boolean)
00221     elif type_name == 'byte':
00222         dv = ua.Variant(0, ua.VariantType.Byte)
00223     elif type_name == 'int':
00224         dv = ua.Variant(0, ua.VariantType.Int32)
00225     elif type_name == 'int8':
00226         dv = ua.Variant(0, ua.VariantType.SByte)
00227     elif type_name == 'uint8':
00228         dv = ua.Variant(0, ua.VariantType.Byte)
00229     elif type_name == 'int16':
00230         dv = ua.Variant(0, ua.VariantType.Int16)
00231     elif type_name == 'uint16':
00232         dv = ua.Variant(0, ua.VariantType.UInt16)
00233     elif type_name == 'int32':
00234         dv = ua.Variant(0, ua.VariantType.Int32)
00235     elif type_name == 'uint32':
00236         dv = ua.Variant(0, ua.VariantType.UInt32)
00237     elif type_name == 'int64':
00238         dv = ua.Variant(0, ua.VariantType.Int64)
00239     elif type_name == 'uint64':
00240         dv = ua.Variant(0, ua.VariantType.UInt64)
00241     elif type_name == 'float' or type_name == 'float32' or type_name == 'float64':
00242         dv = ua.Variant(0.0, ua.VariantType.Float)
00243     elif type_name == 'double':
00244         dv = ua.Variant(0.0, ua.VariantType.Double)
00245     elif type_name == 'string':
00246         dv = ua.Variant('', ua.VariantType.String)
00247     else:
00248         rospy.logerr("can't create node with type" + str(type_name))
00249         return None
00250 
00251     if array_size is not None:
00252         value = []
00253         for i in range(array_size):
00254             value.append(i)
00255     return parent.add_variable(ua.NodeId(topic_name, parent.nodeid.NamespaceIndex),
00256                                ua.QualifiedName(topic_text, parent.nodeid.NamespaceIndex), dv.Value)
00257 
00258 
00259 # Used to delete obsolete topics
00260 def numberofsubscribers(nametolookfor, topicsDict):
00261     # rosout only has one subscriber/publisher at all times, so ignore.
00262     if nametolookfor != "/rosout":
00263         ret = topicsDict[nametolookfor]._subscriber.get_num_connections()
00264     else:
00265         ret = 2
00266     return ret
00267 
00268 
00269 def refresh_topics_and_actions(namespace_ros, server, topicsdict, actionsdict, idx_topics, idx_actions, topics,
00270                                actions):
00271     ros_topics = rospy.get_published_topics(namespace_ros)
00272     rospy.logdebug(str(ros_topics))
00273     rospy.logdebug(str(rospy.get_published_topics('/move_base_simple')))
00274     for topic_name, topic_type in ros_topics:
00275         if topic_name not in topicsdict or topicsdict[topic_name] is None:
00276             if "cancel" in topic_name or "result" in topic_name or "feedback" in topic_name or "goal" in topic_name or "status" in topic_name:
00277                 rospy.logdebug("Found an action: " + str(topic_name))
00278                 correct_name = ros_actions.get_correct_name(topic_name)
00279                 if correct_name not in actionsdict:
00280                     rospy.loginfo("Creating Action with name: " + correct_name)
00281                     try:
00282                         actionsdict[correct_name] = ros_actions.OpcUaROSAction(server, actions, idx_actions,
00283                                                                                correct_name,
00284                                                                                get_goal_type(correct_name),
00285                                                                                get_feedback_type(
00286                                                                                    correct_name))
00287                     except (ValueError, TypeError, AttributeError) as e:
00288                         print(e)
00289                         rospy.logerr("Error while creating Action Objects for Action " + topic_name)
00290 
00291             else:
00292                 # rospy.loginfo("Ignoring normal topics for debugging...")
00293                 topic = OpcUaROSTopic(server, topics, idx_topics, topic_name, topic_type)
00294                 topicsdict[topic_name] = topic
00295         elif numberofsubscribers(topic_name, topicsdict) <= 1 and "rosout" not in topic_name:
00296             topicsdict[topic_name].recursive_delete_items(server.server.get_node(ua.NodeId(topic_name, idx_topics)))
00297             del topicsdict[topic_name]
00298             ros_server.own_rosnode_cleanup()
00299 
00300     ros_topics = rospy.get_published_topics(namespace_ros)
00301     # use to not get dict changed during iteration errors
00302     tobedeleted = []
00303     for topic_nameOPC in topicsdict:
00304         found = False
00305         for topicROS, topic_type in ros_topics:
00306             if topic_nameOPC == topicROS:
00307                 found = True
00308         if not found:
00309             topicsdict[topic_nameOPC].recursive_delete_items(server.get_node(ua.NodeId(topic_nameOPC, idx_topics)))
00310             tobedeleted.append(topic_nameOPC)
00311     for name in tobedeleted:
00312         del topicsdict[name]
00313     ros_actions.refresh_dict(namespace_ros, actionsdict, topicsdict, server, idx_actions)
00314 
00315 
00316 def get_feedback_type(action_name):
00317     try:
00318         type, name, fn = rostopic.get_topic_type(action_name + "/feedback")
00319         return type
00320     except rospy.ROSException as e:
00321         try:
00322             type, name, fn = rostopic.get_topic_type(action_name + "/Feedback", e)
00323             return type
00324         except rospy.ROSException as e2:
00325             rospy.logerr("Couldnt find feedback type for action " + action_name, e2)
00326             return None
00327 
00328 
00329 def get_goal_type(action_name):
00330     try:
00331         type, name, fn = rostopic.get_topic_type(action_name + "/goal")
00332         return type
00333     except rospy.ROSException as e:
00334         try:
00335             type, name, fn = rostopic.get_topic_type(action_name + "/Goal", e)
00336             return type
00337         except rospy.ROSException as e2:
00338             rospy.logerr("Couldnt find feedback type for action " + action_name, e2)
00339             return None


ros_opcua_impl_python_opcua
Author(s): Denis Štogl , Daniel Draper
autogenerated on Sat Jun 8 2019 18:26:23