controller_manager.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 # -*- coding: utf-8 -*-
00003 #####
00004 #####
00005 # This script is copied from https://github.com/arebgun/dynamixel_motor/pull/28
00006 #####
00007 #####
00008 #
00009 # Software License Agreement (BSD License)
00010 #
00011 # Copyright (c) 2010, Antons Rebguns, Cody Jorgensen, Cara Slutter
00012 #               2010-2011, Antons Rebguns
00013 # All rights reserved.
00014 #
00015 # Redistribution and use in source and binary forms, with or without
00016 # modification, are permitted provided that the following conditions
00017 # are met:
00018 #
00019 #  * Redistributions of source code must retain the above copyright
00020 #    notice, this list of conditions and the following disclaimer.
00021 #  * Redistributions in binary form must reproduce the above
00022 #    copyright notice, this list of conditions and the following
00023 #    disclaimer in the documentation and/or other materials provided
00024 #    with the distribution.
00025 #  * Neither the name of University of Arizona nor the names of its
00026 #    contributors may be used to endorse or promote products derived
00027 #    from this software without specific prior written permission.
00028 #
00029 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00030 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00031 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00032 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00033 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00034 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00035 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00036 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00037 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00038 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00039 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00040 # POSSIBILITY OF SUCH DAMAGE.
00041 
00042 
00043 __author__ = 'Antons Rebguns, Cody Jorgensen, Cara Slutter'
00044 __copyright__ = 'Copyright (c) 2010-2011 Antons Rebguns, Cody Jorgensen, Cara Slutter'
00045 
00046 __license__ = 'BSD'
00047 __maintainer__ = 'Antons Rebguns'
00048 __email__ = 'anton@email.arizona.edu'
00049 
00050 
00051 from threading import Thread, Lock
00052 
00053 import sys
00054 from optparse import OptionParser
00055 
00056 import rospy
00057 
00058 #from dynamixel_driver.dynamixel_serial_proxy import SerialProxy
00059 #from dynamixel_driver.dynamixel_dummy_proxy import DummyProxy
00060 
00061 from threading import Thread, Lock
00062 
00063 from dynamixel_driver.dynamixel_serial_proxy import SerialProxy
00064 
00065 import rospy
00066 
00067 class DummyDynamixelIO(object):
00068     """ Provides low level IO with the Dynamixel servos through pyserial. Has the
00069     ability to write instruction packets, request and read register value
00070     packets, send and receive a response to a ping packet, and send a SYNC WRITE
00071     multi-servo instruction packet.
00072     """
00073 
00074     def __init__(self, port, baudrate, max_motor_id, readback_echo=False):
00075         """ Constructor takes serial port and baudrate as arguments. """
00076         self.serial_mutex = Lock()
00077         self.port_name = port
00078         self.readback_echo = readback_echo
00079 
00080         self.goal = [2048]*(max_motor_id+1)
00081         self.position = [2048]*(max_motor_id+1)
00082         self.speed = [0]*(max_motor_id+1)
00083 
00084     def ping(self, servo_id):
00085         self.goal[servo_id] = 2048
00086         self.position[servo_id] = 2048
00087         self.speed[servo_id] = 0
00088         return True;
00089 
00090     def get_model_number(self, servo_id):
00091         """ Reads the servo's model number (e.g. 12 for AX-12+). """
00092         return 12
00093 
00094     def get_angle_limits(self, servo_id):
00095         cwLimit = 0
00096         ccwLimit = 4096
00097         return {'min':cwLimit, 'max':ccwLimit}
00098 
00099     def get_voltage(self, servo_id):
00100         return 12
00101 
00102     def get_voltage_limits(self, servo_id):
00103         # return the data in a dictionary
00104         min_voltage = 8
00105         max_voltage = 12
00106         return {'min':min_voltage, 'max':max_voltage}
00107 
00108     def get_position(self, servo_id):
00109         return self.position[servo_id]
00110 
00111     def get_speed(self, servo_id):
00112         return self.speed[servo_id]
00113 
00114     def get_current(self, servo_id):
00115         return 0
00116 
00117     def get_firmware_version(self, servo_id):
00118         return None
00119 
00120     def get_return_delay_time(self, servo_id):
00121         """ Reads the servo's return delay time. """
00122         return 5
00123 
00124     def get_feedback(self, servo_id):
00125         """
00126         Returns the id, goal, position, error, speed, load, voltage, temperature
00127         and moving values from the specified servo.
00128         """
00129 
00130         error = 0
00131         if self.speed[servo_id] != 0 :
00132             speed = self.speed[servo_id]
00133             self.goal[servo_id] += speed
00134         else:
00135             speed = (self.goal[servo_id] -  self.position[servo_id])*0.1
00136         self.position[servo_id] += speed
00137         return { 'timestamp': rospy.Time.now().to_sec(),
00138                  'id': servo_id,
00139                  'goal': self.goal[servo_id],
00140                  'position': self.position[servo_id] % 4096,
00141                  'error': error,
00142                  'speed': speed,
00143                  'load': 0}
00144 
00145     def set_multi_speed(self, valueTuples):
00146         for servo_id,val in valueTuples:
00147             self.speed[servo_id] = val
00148         return True
00149 
00150     def set_multi_position(self, valueTuples):
00151         for servo_id,val in valueTuples:
00152             self.goal[servo_id] = val
00153             self.speed[servo_id] = 0
00154         return True
00155 
00156 
00157 class DummyProxy(SerialProxy):
00158     def connect(self):
00159         self.dxl_io = DummyDynamixelIO(self.port_name, self.baud_rate, self.max_motor_id) # self.readback_echo: release deb version does not have readback_echo
00160         self._SerialProxy__find_motors()
00161         self.running = True
00162         if self.update_rate > 0: Thread(target=self._SerialProxy__update_motor_states).start()
00163         if self.diagnostics_rate > 0: Thread(target=self._SerialProxy__publish_diagnostic_information).start()
00164 ##
00165 
00166 
00167 from diagnostic_msgs.msg import DiagnosticArray
00168 from diagnostic_msgs.msg import DiagnosticStatus
00169 from diagnostic_msgs.msg import KeyValue
00170 
00171 from dynamixel_controllers.srv import StartController
00172 from dynamixel_controllers.srv import StartControllerResponse
00173 from dynamixel_controllers.srv import StopController
00174 from dynamixel_controllers.srv import StopControllerResponse
00175 from dynamixel_controllers.srv import RestartController
00176 from dynamixel_controllers.srv import RestartControllerResponse
00177 
00178 class ControllerManager:
00179     def __init__(self):
00180         rospy.init_node('dynamixel_controller_manager', anonymous=True)
00181         rospy.on_shutdown(self.on_shutdown)
00182         
00183         self.waiting_meta_controllers = []
00184         self.controllers = {}
00185         self.serial_proxies = {}
00186         self.diagnostics_rate = rospy.get_param('~diagnostics_rate', 1)
00187         
00188         self.start_controller_lock = Lock()
00189         self.stop_controller_lock = Lock()
00190 
00191         manager_namespace = rospy.get_param('~namespace')
00192         serial_ports = rospy.get_param('~serial_ports')
00193         
00194         for port_namespace,port_config in serial_ports.items():
00195             port_name = port_config['port_name']
00196             baud_rate = port_config['baud_rate']
00197             readback_echo = port_config['readback_echo'] if 'readback_echo' in port_config else False
00198             min_motor_id = port_config['min_motor_id'] if 'min_motor_id' in port_config else 0
00199             max_motor_id = port_config['max_motor_id'] if 'max_motor_id' in port_config else 253
00200             update_rate = port_config['update_rate'] if 'update_rate' in port_config else 5
00201             error_level_temp = 75
00202             warn_level_temp = 70
00203             
00204             if 'diagnostics' in port_config:
00205                 if 'error_level_temp' in port_config['diagnostics']:
00206                     error_level_temp = port_config['diagnostics']['error_level_temp']
00207                 if 'warn_level_temp' in port_config['diagnostics']:
00208                     warn_level_temp = port_config['diagnostics']['warn_level_temp']
00209                     
00210             serial_proxy = SerialProxy(port_name=port_name,
00211                                        port_namespace=port_namespace,
00212                                        baud_rate=baud_rate,
00213                                        min_motor_id=min_motor_id,
00214                                        max_motor_id=max_motor_id,
00215                                        update_rate=update_rate,
00216                                        diagnostics_rate=self.diagnostics_rate,
00217                                        error_level_temp=error_level_temp,
00218                                        warn_level_temp=warn_level_temp)
00219                                        # readback_echo=readback_echo ## this will not work on deb version
00220             serial_proxy.connect()
00221             
00222             # will create a set of services for each serial port under common manager namesapce
00223             # e.g. /dynamixel_manager/robot_arm_port/start_controller
00224             #      /dynamixel_manager/robot_head_port/start_controller
00225             # where 'dynamixel_manager' is manager's namespace
00226             #       'robot_arm_port' and 'robot_head_port' are human readable names for serial ports
00227             rospy.Service('%s/%s/start_controller' % (manager_namespace, port_namespace), StartController, self.start_controller)
00228             rospy.Service('%s/%s/stop_controller' % (manager_namespace, port_namespace), StopController, self.stop_controller)
00229             rospy.Service('%s/%s/restart_controller' % (manager_namespace, port_namespace), RestartController, self.restart_controller)
00230             
00231             self.serial_proxies[port_namespace] = serial_proxy
00232             
00233         # services for 'meta' controllers, e.g. joint trajectory controller
00234         # these controllers don't have their own serial port, instead they rely
00235         # on regular controllers for serial connection. The advantage of meta
00236         # controller is that it can pack commands for multiple motors on multiple
00237         # serial ports.
00238         # NOTE: all serial ports that meta controller needs should be managed by
00239         # the same controler manager.
00240         rospy.Service('%s/meta/start_controller' % manager_namespace, StartController, self.start_controller)
00241         rospy.Service('%s/meta/stop_controller' % manager_namespace, StopController, self.stop_controller)
00242         rospy.Service('%s/meta/restart_controller' % manager_namespace, RestartController, self.restart_controller)
00243         
00244         self.diagnostics_pub = rospy.Publisher('/diagnostics', DiagnosticArray)
00245         if self.diagnostics_rate > 0: Thread(target=self.diagnostics_processor).start()
00246 
00247     def on_shutdown(self):
00248         for serial_proxy in self.serial_proxies.values():
00249             serial_proxy.disconnect()
00250 
00251     def diagnostics_processor(self):
00252         diag_msg = DiagnosticArray()
00253         
00254         rate = rospy.Rate(self.diagnostics_rate)
00255         while not rospy.is_shutdown():
00256             diag_msg.status = []
00257             diag_msg.header.stamp = rospy.Time.now()
00258             
00259             for controller in self.controllers.values():
00260                 try:
00261                     joint_state = controller.joint_state
00262                     temps = joint_state.motor_temps
00263                     max_temp = max(temps)
00264                     
00265                     status = DiagnosticStatus()
00266                     status.name = 'Joint Controller (%s)' % controller.joint_name
00267                     status.hardware_id = 'Robotis Dynamixel %s on port %s' % (str(joint_state.motor_ids), controller.port_namespace)
00268                     status.values.append(KeyValue('Goal', str(joint_state.goal_pos)))
00269                     status.values.append(KeyValue('Position', str(joint_state.current_pos)))
00270                     status.values.append(KeyValue('Error', str(joint_state.error)))
00271                     status.values.append(KeyValue('Velocity', str(joint_state.velocity)))
00272                     status.values.append(KeyValue('Load', str(joint_state.load)))
00273                     status.values.append(KeyValue('Moving', str(joint_state.is_moving)))
00274                     status.values.append(KeyValue('Temperature', str(max_temp)))
00275                     status.level = DiagnosticStatus.OK
00276                     status.message = 'OK'
00277                         
00278                     diag_msg.status.append(status)
00279                 except:
00280                     pass
00281                     
00282             self.diagnostics_pub.publish(diag_msg)
00283             rate.sleep()
00284 
00285     def check_deps(self):
00286         controllers_still_waiting = []
00287         
00288         for i,(controller_name,deps,kls) in enumerate(self.waiting_meta_controllers):
00289             if not set(deps).issubset(self.controllers.keys()):
00290                 controllers_still_waiting.append(self.waiting_meta_controllers[i])
00291                 rospy.logwarn('[%s] not all dependencies started, still waiting for %s...' % (controller_name, str(list(set(deps).difference(self.controllers.keys())))))
00292             else:
00293                 dependencies = [self.controllers[dep_name] for dep_name in deps]
00294                 controller = kls(controller_name, dependencies)
00295                 
00296                 if controller.initialize():
00297                     controller.start()
00298                     self.controllers[controller_name] = controller
00299                     
00300         self.waiting_meta_controllers = controllers_still_waiting[:]
00301 
00302     def start_controller(self, req):
00303         port_name = req.port_name
00304         package_path = req.package_path
00305         module_name = req.module_name
00306         class_name = req.class_name
00307         controller_name = req.controller_name
00308         
00309         self.start_controller_lock.acquire()
00310         
00311         if controller_name in self.controllers:
00312             self.start_controller_lock.release()
00313             return StartControllerResponse(False, 'Controller [%s] already started. If you want to restart it, call restart.' % controller_name)
00314             
00315         try:
00316             if module_name not in sys.modules:
00317                 # import if module not previously imported
00318                 package_module = __import__(package_path, globals(), locals(), [module_name], -1)
00319             else:
00320                 # reload module if previously imported
00321                 package_module = reload(sys.modules[package_path])
00322             controller_module = getattr(package_module, module_name)
00323         except ImportError, ie:
00324             self.start_controller_lock.release()
00325             return StartControllerResponse(False, 'Cannot find controller module. Unable to start controller %s\n%s' % (module_name, str(ie)))
00326         except SyntaxError, se:
00327             self.start_controller_lock.release()
00328             return StartControllerResponse(False, 'Syntax error in controller module. Unable to start controller %s\n%s' % (module_name, str(se)))
00329         except Exception, e:
00330             self.start_controller_lock.release()
00331             return StartControllerResponse(False, 'Unknown error has occured. Unable to start controller %s\n%s' % (module_name, str(e)))
00332         
00333         kls = getattr(controller_module, class_name)
00334         
00335         if port_name == 'meta':
00336             self.waiting_meta_controllers.append((controller_name,req.dependencies,kls))
00337             self.check_deps()
00338             self.start_controller_lock.release()
00339             return StartControllerResponse(True, '')
00340             
00341         if port_name != 'meta' and (port_name not in self.serial_proxies):
00342             self.start_controller_lock.release()
00343             return StartControllerResponse(False, 'Specified port [%s] not found, available ports are %s. Unable to start controller %s' % (port_name, str(self.serial_proxies.keys()), controller_name))
00344             
00345         controller = kls(self.serial_proxies[port_name].dxl_io, controller_name, port_name)
00346         
00347         if controller.initialize():
00348             controller.start()
00349             self.controllers[controller_name] = controller
00350             
00351             self.check_deps()
00352             self.start_controller_lock.release()
00353             
00354             return StartControllerResponse(True, 'Controller %s successfully started.' % controller_name)
00355         else:
00356             self.start_controller_lock.release()
00357             return StartControllerResponse(False, 'Initialization failed. Unable to start controller %s' % controller_name)
00358 
00359     def stop_controller(self, req):
00360         controller_name = req.controller_name
00361         self.stop_controller_lock.acquire()
00362         
00363         if controller_name in self.controllers:
00364             self.controllers[controller_name].stop()
00365             del self.controllers[controller_name]
00366             self.stop_controller_lock.release()
00367             return StopControllerResponse(True, 'controller %s successfully stopped.' % controller_name)
00368         else:
00369             self.self.stop_controller_lock.release()
00370             return StopControllerResponse(False, 'controller %s was not running.' % controller_name)
00371 
00372     def restart_controller(self, req):
00373         response1 = self.stop_controller(StopController(req.controller_name))
00374         response2 = self.start_controller(req)
00375         return RestartControllerResponse(response1.success and response2.success, '%s\n%s' % (response1.reason, response2.reason))
00376 
00377 if __name__ == '__main__':
00378     try:
00379         usage_msg = 'Usage: %prog [options]'
00380         parser = OptionParser(usage=usage_msg)
00381         parser.add_option('-d', '--dummy', default=False,
00382                           help='running manager in dummy mode, which did not connect to actual haradware device')
00383         (options, args) = parser.parse_args(rospy.myargv()[1:])
00384         if options.dummy:
00385             SerialProxy = DummyProxy
00386         manager = ControllerManager()
00387         rospy.spin()
00388     except rospy.ROSInterruptException: pass
00389 


spur_controller
Author(s): Tokyo Opensource Robotics Programmer 534o <534o@opensource-robotics.tokyo.jp>, Isaac I. Y. Saito
autogenerated on Sat Jun 8 2019 19:44:12