joy_teleop.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 import importlib
00003 
00004 import rospy
00005 import genpy.message
00006 from rospy import ROSException
00007 import sensor_msgs.msg
00008 import actionlib
00009 import rostopic
00010 import rosservice
00011 from threading import Thread
00012 from rosservice import ROSServiceException
00013 
00014 class JoyTeleopException(Exception):
00015     pass
00016 
00017 
00018 class JoyTeleop:
00019     """
00020     Generic joystick teleoperation node.
00021     Will not start without configuration, has to be stored in 'teleop' parameter.
00022     See config/joy_teleop.yaml for an example.
00023     """
00024     def __init__(self):
00025         if not rospy.has_param("teleop"):
00026             rospy.logfatal("no configuration was found, taking node down")
00027             raise JoyTeleopException("no config")
00028 
00029         self.publishers = {}
00030         self.al_clients = {}
00031         self.srv_clients = {}
00032         self.service_types = {}
00033         self.message_types = {}
00034         self.command_list = {}
00035         self.offline_actions = []
00036         self.offline_services = []
00037 
00038         self.old_buttons = []
00039 
00040         teleop_cfg = rospy.get_param("teleop")
00041 
00042         for i in teleop_cfg:
00043             if i in self.command_list:
00044                 rospy.logerr("command {} was duplicated".format(i))
00045                 continue
00046             action_type = teleop_cfg[i]['type']
00047             self.add_command(i, teleop_cfg[i])
00048             if action_type == 'topic':
00049                 self.register_topic(i, teleop_cfg[i])
00050             elif action_type == 'action':
00051                 self.register_action(i, teleop_cfg[i])
00052             elif action_type == 'service':
00053                 self.register_service(i, teleop_cfg[i])
00054             else:
00055                 rospy.logerr("unknown type '%s' for command '%s'", action_type, i)
00056 
00057         # Don't subscribe until everything has been initialized.
00058         rospy.Subscriber('joy', sensor_msgs.msg.Joy, self.joy_callback)
00059 
00060         # Run a low-freq action updater
00061         rospy.Timer(rospy.Duration(2.0), self.update_actions)
00062 
00063     def joy_callback(self, data):
00064         try:
00065             for c in self.command_list:
00066                 if self.match_command(c, data.buttons):
00067                     self.run_command(c, data)
00068         except JoyTeleopException as e:
00069             rospy.logerr("error while parsing joystick input: %s", str(e))
00070         self.old_buttons = data.buttons
00071 
00072     def register_topic(self, name, command):
00073         """Add a topic publisher for a joystick command"""
00074         topic_name = command['topic_name']
00075         try:
00076             topic_type = self.get_message_type(command['message_type'])
00077             self.publishers[topic_name] = rospy.Publisher(topic_name, topic_type, queue_size=1)
00078         except JoyTeleopException as e:
00079             rospy.logerr("could not register topic for command {}: {}".format(name, str(e)))
00080 
00081     def register_action(self, name, command):
00082         """Add an action client for a joystick command"""
00083         action_name = command['action_name']
00084         try:
00085             action_type = self.get_message_type(self.get_action_type(action_name))
00086             self.al_clients[action_name] = actionlib.SimpleActionClient(action_name, action_type)
00087             if action_name in self.offline_actions:
00088                 self.offline_actions.remove(action_name)
00089         except JoyTeleopException:
00090             if action_name not in self.offline_actions:
00091                 self.offline_actions.append(action_name)
00092 
00093     class AsyncServiceProxy(object):
00094         def __init__(self, name, service_class, persistent=True):
00095             try:
00096                 rospy.wait_for_service(name, timeout=2.0)
00097             except ROSException:
00098                 raise JoyTeleopException("Service {} is not available".format(name))
00099             self._service_proxy = rospy.ServiceProxy(name, service_class, persistent)
00100             self._thread = Thread(target=self._service_proxy, args=[])
00101 
00102         def __del__(self):
00103             # try to join our thread - no way I know of to interrupt a service
00104             # request
00105             if self._thread.is_alive():
00106                 self._thread.join(1.0)
00107 
00108         def __call__(self, request):
00109             if self._thread.is_alive():
00110                 self._thread.join(0.01)
00111                 if self._thread.is_alive():
00112                     return False
00113 
00114             self._thread = Thread(target=self._service_proxy, args=[request])
00115             self._thread.start()
00116             return True
00117 
00118     def register_service(self, name, command):
00119         """ Add an AsyncServiceProxy for a joystick command """
00120         service_name = command['service_name']
00121         try:
00122             service_type = self.get_service_type(service_name)
00123             self.srv_clients[service_name] = self.AsyncServiceProxy(
00124                 service_name,
00125                 service_type)
00126 
00127             if service_name in self.offline_services:
00128                 self.offline_services.remove(service_name)
00129         except JoyTeleopException:
00130             if service_name not in self.offline_services:
00131                 self.offline_services.append(service_name)
00132 
00133     def match_command(self, c, buttons):
00134         """Find a command matching a joystick configuration"""
00135         for b in self.command_list[c]['buttons']:
00136             if b < 0 or len(buttons) <= b or buttons[b] != 1:
00137                 return False
00138         return sum(buttons) == len(self.command_list[c]['buttons'])
00139 
00140     def add_command(self, name, command):
00141         """Add a command to the command list"""
00142         if command['type'] == 'topic':
00143             if 'deadman_buttons' not in command:
00144                 command['deadman_buttons'] = []
00145             command['buttons'] = command['deadman_buttons']
00146         elif command['type'] == 'action':
00147             if 'action_goal' not in command:
00148                 command['action_goal'] = {}
00149         elif command['type'] == 'service':
00150             if 'service_request' not in command:
00151                 command['service_request'] = {}
00152         self.command_list[name] = command
00153 
00154     def run_command(self, command, joy_state):
00155         """Run a joystick command"""
00156         cmd = self.command_list[command]
00157         if cmd['type'] == 'topic':
00158             self.run_topic(command, joy_state)
00159         elif cmd['type'] == 'action':
00160             if cmd['action_name'] in self.offline_actions:
00161                 rospy.logerr("command {} was not played because the action "
00162                              "server was unavailable. Trying to reconnect..."
00163                              .format(cmd['action_name']))
00164                 self.register_action(command, self.command_list[command])
00165             else:
00166                 if joy_state.buttons != self.old_buttons:
00167                     self.run_action(command, joy_state)
00168         elif cmd['type'] == 'service':
00169             if cmd['service_name'] in self.offline_services:
00170                 rospy.logerr("command {} was not played because the service "
00171                              "server was unavailable. Trying to reconnect..."
00172                              .format(cmd['service_name']))
00173                 self.register_service(command, self.command_list[command])
00174             else:
00175                 if joy_state.buttons != self.old_buttons:
00176                     self.run_service(command, joy_state)
00177         else:
00178             raise JoyTeleopException('command {} is neither a topic publisher nor an action or service client'
00179                                      .format(command))
00180 
00181     def run_topic(self, c, joy_state):
00182         cmd = self.command_list[c]
00183         msg = self.get_message_type(cmd['message_type'])()
00184 
00185         if 'message_value' in cmd:
00186             for param in cmd['message_value']:
00187                 self.set_member(msg, param['target'], param['value'])
00188 
00189         else:
00190             for mapping in cmd['axis_mappings']:
00191                 if len(joy_state.axes)<=mapping['axis']:
00192                   rospy.logerr('Joystick has only {} axes (indexed from 0), but #{} was referenced in config.'.format(len(joy_state.axes), mapping['axis']))
00193                   val = 0.0
00194                 else:
00195                   val = joy_state.axes[mapping['axis']] * mapping.get('scale', 1.0) + mapping.get('offset', 0.0)
00196 
00197                 self.set_member(msg, mapping['target'], val)
00198                 
00199         self.publishers[cmd['topic_name']].publish(msg)
00200 
00201     def run_action(self, c, joy_state):
00202         cmd = self.command_list[c]
00203         goal = self.get_message_type(self.get_action_type(cmd['action_name'])[:-6] + 'Goal')()
00204         genpy.message.fill_message_args(goal, [cmd['action_goal']])
00205         self.al_clients[cmd['action_name']].send_goal(goal)
00206 
00207     def run_service(self, c, joy_state):
00208         cmd = self.command_list[c]
00209         request = self.get_service_type(cmd['service_name'])._request_class()
00210         # should work for requests, too
00211         genpy.message.fill_message_args(request, [cmd['service_request']])
00212         if not self.srv_clients[cmd['service_name']](request):
00213             rospy.loginfo('Not sending new service request for command {} because previous request has not finished'
00214                           .format(c))
00215 
00216     def set_member(self, msg, member, value):
00217         ml = member.split('.')
00218         if len(ml) < 1:
00219             return
00220         target = msg
00221         for i in ml[:-1]:
00222             target = getattr(target, i)
00223         setattr(target, ml[-1], value)
00224 
00225     def get_message_type(self, type_name):
00226         if type_name not in self.message_types:
00227             try:
00228                 package, message = type_name.split('/')
00229                 mod = importlib.import_module(package + '.msg')
00230                 self.message_types[type_name] = getattr(mod, message)
00231             except ValueError:
00232                 raise JoyTeleopException("message type format error")
00233             except ImportError:
00234                 raise JoyTeleopException("module {} could not be loaded".format(package))
00235             except AttributeError:
00236                 raise JoyTeleopException("message {} could not be loaded from module {}".format(package, message))
00237         return self.message_types[type_name]
00238 
00239     def get_action_type(self, action_name):
00240         try:
00241             return rostopic._get_topic_type(rospy.resolve_name(action_name) + '/goal')[0][:-4]
00242         except TypeError:
00243             raise JoyTeleopException("could not find action {}".format(action_name))
00244 
00245     def get_service_type(self, service_name):
00246         if service_name not in self.service_types:
00247             try:
00248                 self.service_types[service_name] = rosservice.get_service_class_by_name(service_name)
00249             except ROSServiceException, e:
00250                 raise JoyTeleopException("service {} could not be loaded: {}".format(service_name, str(e)))
00251         return self.service_types[service_name]
00252 
00253     def update_actions(self, evt=None):
00254         for name, cmd in self.command_list.iteritems():
00255             if cmd['type'] != 'action':
00256                 continue
00257             if cmd['action_name'] in self.offline_actions:
00258                 self.register_action(name, cmd)
00259 
00260 
00261 if __name__ == "__main__":
00262     try:
00263         rospy.init_node('joy_teleop')
00264         jt = JoyTeleop()
00265         rospy.spin()
00266     except JoyTeleopException:
00267         pass
00268     except rospy.ROSInterruptException:
00269         pass


joy_teleop
Author(s): Paul Mathieu
autogenerated on Sun Apr 23 2017 02:40:17