joy_teleop.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 import importlib
00003 
00004 import rospy
00005 import sensor_msgs.msg
00006 import actionlib
00007 import rostopic
00008 
00009 class JoyTeleopException(Exception):
00010     pass
00011 
00012 
00013 class JoyTeleop:
00014     """
00015     Generic joystick teleoperation node.
00016     Will not start without configuration, has to be stored in 'teleop' parameter.
00017     See config/joy_teleop.yaml for an example.
00018     """
00019     def __init__(self):
00020         if not rospy.has_param("teleop"):
00021             rospy.logfatal("no configuration was found, taking node down")
00022             raise JoyTeleopException("no config")
00023 
00024         self.publishers = {}
00025         self.al_clients = {}
00026         self.message_types = {}
00027         self.command_list = {}
00028         self.offline_actions = []
00029 
00030         self.old_buttons = []
00031 
00032         teleop_cfg = rospy.get_param("teleop")
00033 
00034         for i in teleop_cfg:
00035             if i in self.command_list:
00036                 rospy.logerr("command {} was duplicated".format(i))
00037                 continue
00038             action_type = teleop_cfg[i]['type']
00039             self.add_command(i, teleop_cfg[i])
00040             if action_type == 'topic':
00041                 self.register_topic(i, teleop_cfg[i])
00042             elif action_type == 'action':
00043                 self.register_action(i, teleop_cfg[i])
00044             else:
00045                 rospy.logerr("unknown type '%s' for command '%s'", action_type, i)
00046 
00047         # Don't subscribe until everything has been initialized.
00048         rospy.Subscriber('joy', sensor_msgs.msg.Joy, self.joy_callback)
00049 
00050         # Run a low-freq action updater
00051         rospy.Timer(rospy.Duration(2.0), self.update_actions)
00052 
00053     def joy_callback(self, data):
00054         try:
00055             for c in self.command_list:
00056                 if self.match_command(c, data.buttons):
00057                     self.run_command(c, data)
00058         except JoyTeleopException as e:
00059             rospy.logerr("error while parsing joystick input: %s", str(e))
00060         self.old_buttons = data.buttons
00061 
00062     def register_topic(self, name, command):
00063         """Add a topic publisher for a joystick command"""
00064         topic_name = command['topic_name']
00065         try:
00066             topic_type = self.get_message_type(command['message_type'])
00067             self.publishers[topic_name] = rospy.Publisher(topic_name, topic_type)
00068         except JoyTeleopException as e:
00069             rospy.logerr("could not register topic for command {}: {}".format(name, str(e)))
00070 
00071     def register_action(self, name, command):
00072         """Add an action client for a joystick command"""
00073         action_name = command['action_name']
00074         try:
00075             action_type = self.get_message_type(self.get_action_type(action_name))
00076             self.al_clients[action_name] = actionlib.SimpleActionClient(action_name, action_type)
00077             if action_name in self.offline_actions:
00078                 self.offline_actions.remove(action_name)
00079         except JoyTeleopException:
00080             if action_name not in self.offline_actions:
00081                 self.offline_actions.append(action_name)
00082 
00083     def match_command(self, c, buttons):
00084         """Find a command matching a joystick configuration"""
00085         for b in self.command_list[c]['buttons']:
00086             if b < 0 or len(buttons) <= b or buttons[b] != 1:
00087                 return False
00088         return sum(buttons) == len(self.command_list[c]['buttons'])
00089 
00090     def add_command(self, name, command):
00091         """Add a command to the command list"""
00092         if command['type'] == 'topic':
00093             if 'deadman_buttons' not in command:
00094                 command['deadman_buttons'] = []
00095             command['buttons'] = command['deadman_buttons']
00096         elif command['type'] == 'action':
00097             if 'action_goal' not in command:
00098                 command['action_goal'] = {}
00099         self.command_list[name] = command
00100 
00101     def run_command(self, command, joy_state):
00102         """Run a joystick command"""
00103         cmd = self.command_list[command]
00104         if cmd['type'] == 'topic':
00105             self.run_topic(command, joy_state)
00106         elif cmd['type'] == 'action':
00107             if cmd['action_name'] in self.offline_actions:
00108                 rospy.logerr("command {} was not played because the action "
00109                              "server was unavailable. Trying to reconnect..."
00110                              .format(cmd['action_name']))
00111                 self.register_action(command, self.command_list[command])
00112             else:
00113                 if joy_state.buttons != self.old_buttons:
00114                     self.run_action(command, joy_state)
00115         else:
00116             raise JoyTeleopException('command {} is neither a topic publisher nor an action client'
00117                                      .format(command))
00118 
00119     def run_topic(self, c, joy_state):
00120         cmd = self.command_list[c]
00121         msg = self.get_message_type(cmd['message_type'])()
00122         for mapping in cmd['axis_mappings']:
00123             val = joy_state.axes[mapping['axis']] * mapping.get('scale', 1.0) + mapping.get('offset', 0.0)
00124             self.set_member(msg, mapping['target'], val)
00125         self.publishers[cmd['topic_name']].publish(msg)
00126 
00127     def run_action(self, c, joy_state):
00128         cmd = self.command_list[c]
00129         goal = self.get_message_type(self.get_action_type(cmd['action_name'])[:-6] + 'Goal')()
00130         self.fill_msg(goal, cmd['action_goal'])
00131         self.al_clients[cmd['action_name']].send_goal(goal)
00132 
00133     def fill_msg(self, msg, values):
00134         for k, v in values.iteritems():
00135             if type(v) is dict:
00136                 self.fill_msg(getattr(msg, k), v)
00137             else:
00138                 setattr(msg, k, v)
00139 
00140     def set_member(self, msg, member, value):
00141         ml = member.split('.')
00142         if len(ml) < 1:
00143             return
00144         target = msg
00145         for i in ml[:-1]:
00146             target = getattr(target, i)
00147         setattr(target, ml[-1], value)
00148 
00149     def get_message_type(self, type_name):
00150         if type_name not in self.message_types:
00151             try:
00152                 package, message = type_name.split('/')
00153                 mod = importlib.import_module(package + '.msg')
00154                 self.message_types[type_name] = getattr(mod, message)
00155             except ValueError:
00156                 raise JoyTeleopException("message type format error")
00157             except ImportError:
00158                 raise JoyTeleopException("module {} could not be loaded".format(package))
00159             except AttributeError:
00160                 raise JoyTeleopException("message {} could not be loaded from module {}".format(package, message))
00161         return self.message_types[type_name]
00162 
00163     def get_action_type(self, action_name):
00164         try:
00165             return rostopic._get_topic_type(rospy.resolve_name(action_name) + '/goal')[0][:-4]
00166         except TypeError:
00167             raise JoyTeleopException("could not find action {}".format(action_name))
00168 
00169     def update_actions(self, evt=None):
00170         for name, cmd in self.command_list.iteritems():
00171             if cmd['type'] != 'action':
00172                 continue
00173             if cmd['action_name'] in self.offline_actions:
00174                 self.register_action(name, cmd)
00175 
00176 
00177 if __name__ == "__main__":
00178     try:
00179         rospy.init_node('joy_teleop')
00180         jt = JoyTeleop()
00181         rospy.spin()
00182     except JoyTeleopException:
00183         pass
00184     except rospy.ROSInterruptException:
00185         pass


joy_teleop
Author(s): Paul Mathieu
autogenerated on Thu Aug 27 2015 15:23:52