rover_zero.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 from roboclaw_driver.roboclaw import Roboclaw
3 import rospy
4 from geometry_msgs.msg import Twist, Quaternion
5 from threading import Lock
6 from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus, KeyValue
7 from nav_msgs.msg import Odometry
8 import PyKDL
9 import math
10 from std_msgs.msg import Bool
11 
13  def __init__(self):
14  self._node = rospy.init_node('Rover_Zero_Controller', anonymous=True)
15 
16  # ROS params
17  self._port = rospy.get_param('~dev', '/dev/ttyACM0')
18  self._address = rospy.get_param('~address', 128)
19  self._baud = rospy.get_param('~baud', 115200)
20  self._max_vel = rospy.get_param('~max_vel', 5.0)
21  self._max_turn_rate = rospy.get_param('~max_turn_rate', 6.28)
22  self._duty_coef = rospy.get_param('~speed_to_duty_coef', 1.02)
23  self._diag_frequency = rospy.get_param('~diag_frequency_hz', 1.0)
24  self._motor_cmd_frequency = rospy.get_param('~motor_cmd_frequency_hz', 30.0)
25  self._odom_frequency = rospy.get_param('~odom_frequency_hz', 30.0)
26  self._cmd_vel_timeout = rospy.get_param('~cmd_vel_timeout', 0.5)
27  self._encoder_odom_enabled = rospy.get_param('~enable_encoder_odom', False)
28  self._esc_feedback_controls_enabled = rospy.get_param('~enable_esc_feedback_controls', False)
29  self._v_pid_overwrite = rospy.get_param('~v_pid_overwrite', False)
30  self._save_motor_controller_settings = rospy.get_param('~save_motor_controller_settings', False)
31  self._m1_v_p = rospy.get_param('~m1_v_p', 3.00)
32  self._m1_v_i = rospy.get_param('~m1_v_i', 0.35)
33  self._m1_v_d = rospy.get_param('~m1_v_d', 0.00)
34  self._m1_v_qpps = int(rospy.get_param('~m1_v_qpps', 10000))
35  self._m2_v_p = rospy.get_param('~m2_v_p', 3.00)
36  self._m2_v_i = rospy.get_param('~m2_v_i', 0.35)
37  self._m2_v_d = rospy.get_param('~m2_v_d', 0.00)
38  self._m2_v_qpps = int(rospy.get_param('~m2_v_qpps', 10000))
39  self._encoder_pulses_per_turn = rospy.get_param('~encoder_pulses_per_turn', 5400.0)
40  self._left_motor_max_current = rospy.get_param('~left_motor_max_current', 5.0)
41  self._right_motor_max_current = rospy.get_param('~right_motor_max_current', 5.0)
42  self._active_brake_timeout = rospy.get_param('~active_brake_timeout', 1.0)
43  self._odom_frame = rospy.get_param('~odom_frame', "odom")
44  self._base_link_frame = rospy.get_param('~base_link_frame', "base_link")
45  self._wheel_base = rospy.get_param('~wheel_base', 0.358775) # Distance between center of wheels
46  self._wheel_radius = rospy.get_param('~wheel_radius', 0.127) # Radius of wheel
47 
48  # Initialize Roboclaw Serial
49  self._roboclaw = Roboclaw(self._port, self._baud)
50  if not self._roboclaw.Open():
51  rospy.logfatal('Could not open serial at ' + self._port)
52  exit(1)
53 
55 
56  # Class Variables
57  self._left_motor_speed = 0.0
58  self._right_motor_speed = 0.0
59  self._serial_lock = Lock()
60  self._variable_lock = Lock()
61  self._last_cmd_vel_received = rospy.get_rostime()
62  self._estop_on_ = False
63  self._last_odom_update = rospy.Time.now()
65  self._active_brake_enabled = False
67 
68  # Diagnostic Parameters
69  self._firmware_version = None
70  self._left_motor_current = None
72  self._battery_voltage = None
73  self._controller_error = None
74 
75  # Odometry values
76  self._odom_position_x = 0.0
77  self._odom_position_y = 0.0
79 
80  # ROS Publishers
81  if self._diag_frequency > 0.0:
82  self._pub_diag = rospy.Publisher('/diagnostics', DiagnosticArray, queue_size=1)
83  if self._encoder_odom_enabled and self._odom_frequency > 0.0:
84  self._pub_odom = rospy.Publisher('/odom', Odometry, queue_size=1)
85 
86  # ROS Subscribers
87  self._twist_sub = rospy.Subscriber("/cmd_vel", Twist, self._twist_cmd_cb, queue_size=1)
88  self._estop_enable_sub = rospy.Subscriber("/soft_estop/enable", Bool, self._estop_enable_cb, queue_size=1)
89  self._estop_reset_sub = rospy.Subscriber("/soft_estop/reset", Bool, self._estop_reset_cb, queue_size=1)
90 
91  # ROS Timers
92  if self._diag_frequency > 0.0:
93  rospy.Timer(rospy.Duration(1.0 / self._diag_frequency), self._diag_cb)
94  rospy.Timer(rospy.Duration(1.0 / self._motor_cmd_frequency), self._motor_cmd_cb)
95  if self._encoder_odom_enabled and self._odom_frequency > 0.0:
96  rospy.Timer(rospy.Duration(1.0 / self._odom_frequency), self._odom_cb)
97  if self._cmd_vel_timeout > 0.0:
98  rospy.Timer(rospy.Duration(self._cmd_vel_timeout), self._cmd_vel_timeout_cb)
99 
100  # Get Roboclaw Firmware Version
101  self._firmware_version = self._roboclaw.ReadVersion(self._address)[1].replace('\n', '')
102 
104 
105  # Reset Encoders
106  self._roboclaw.ResetEncoders(self._address)
107 
109  self._firmware_version = self._roboclaw.ReadVersion(self._address)
110 
111  self._roboclaw.SetM1MaxCurrent(self._address, int(100 * self._left_motor_max_current))
112  self._roboclaw.SetM2MaxCurrent(self._address, int(100 * self._right_motor_max_current))
113 
115  if self._v_pid_overwrite:
116  self._roboclaw.SetM1VelocityPID(self._address, self._m1_v_p, self._m1_v_i, self._m1_v_d, self._m1_v_qpps)
117  self._roboclaw.SetM2VelocityPID(self._address, self._m2_v_p, self._m2_v_i, self._m2_v_d, self._m2_v_qpps)
118  else:
119  self._get_V_PID()
120 
122  self._roboclaw.WriteNVM(self._address)
123 
124  def _get_V_PID(self):
125  (res, p, i, d, qpps) = self._roboclaw.ReadM1VelocityPID(self._address)
126  if res:
127  self._m1_v_p = p
128  self._m1_v_i = i
129  self._m1_v_d = d
130  self._m1_v_qpps = qpps
131  rospy.loginfo('Motor 1 (P, I, D, QPPS): %f, %f, %f, %d', p, i, d, qpps)
132 
133  (res, p, i, d, qpps) = self._roboclaw.ReadM2VelocityPID(self._address)
134  if res:
135  self._m2_v_p = p
136  self._m2_v_i = i
137  self._m2_v_d = d
138  self._m2_v_qpps = qpps
139  rospy.loginfo('Motor 2 (P, I, D, QPPS): %f, %f, %f, %d', p, i, d, qpps)
140 
142  fatal_misconfiguration = False
143 
144  if self._max_vel <= 0.0:
145  rospy.logfatal('Maximum velocity (max_vel) must be greater than 0.')
146  fatal_misconfiguration = True
147 
148  if self._max_turn_rate <= 0.0:
149  rospy.logfatal('Maximum turn rate (max_turn_rate) must be greater than 0.')
150  fatal_misconfiguration = True
151 
152  if self._duty_coef <= 0.0:
153  rospy.logfatal('The wheel velocity to motor duty coefficient (speed_to_duty_coef) must be greater than 0.')
154  fatal_misconfiguration = True
155 
156  if self._encoder_pulses_per_turn <= 0.0:
157  rospy.logfatal('Encoder pulses per turn (encoder_pulses_per_turn) must be greater than 0.')
158  fatal_misconfiguration = True
159 
160  if self._motor_cmd_frequency < 1.0:
161  rospy.logfatal('Motor command frequency (motor_cmd_frequency_hz) must be greater than or equal to 1Hz.')
162  fatal_misconfiguration = True
163  elif self._motor_cmd_frequency < 5.0:
164  rospy.logwarn('Motor command frequency (motor_cmd_frequency_hz) is low. It is suggested that motor command update rate of at least 5Hz.')
165 
166  if self._cmd_vel_timeout <= 0.0:
167  rospy.logwarn('cmd_vel timeout (cmd_vel_timeout) disabled. Robot will execute last cmd_vel message forever.')
168 
169  if not self._encoder_odom_enabled:
170  rospy.logwarn('Encoder odometry are not enabled (enable_encoder_odom).')
171 
173  if self._v_pid_overwrite:
174  if (self._m1_v_p == 0.0 and self._m1_v_i == 0.0 and self._m1_v_d == 0.0) or self._m1_v_qpps <= 0:
175  rospy.logfatal('Invalid user specified PID parameters for left motor.')
176  fatal_misconfiguration = True
177  else:
178  (res, p, i, d, qpps) = self._roboclaw.ReadM1VelocityPID(self._address)
179  if (p == 0.0 and i == 0.0 and d == 0.0) or qpps <= 0:
180  rospy.logfatal('Invalid PID parameters for left motor saved on Roboclaw.')
181  fatal_misconfiguration = True
182 
184  if self._v_pid_overwrite:
185  if (self._m2_v_p == 0.0 and self._m2_v_i == 0.0 and self._m2_v_d == 0.0) or self._m2_v_qpps <= 0:
186  rospy.logfatal('Invalid user specified PID parameters for right motor.')
187  fatal_misconfiguration = True
188  else:
189  (res, p, i, d, qpps) = self._roboclaw.ReadM2VelocityPID(self._address)
190  if (p == 0.0 and i == 0.0 and d == 0.0) or qpps <= 0:
191  rospy.logfatal('Invalid PID parameters for right motor saved on Roboclaw.')
192  fatal_misconfiguration = True
193 
194  if not self._esc_feedback_controls_enabled:
195  rospy.logwarn('PID feedback controls are not enabled (enable_esc_feedback_controls), running in duty cycles mode.')
196  rospy.logwarn('BEWARE: In duty cycle mode if the serial connection is lost it will indefitely execute the last command sent to the Roboclaw.')
197 
199  rospy.loginfo('New motor controller settings will be saved to Roboclaw eeprom')
200 
201  if self._left_motor_max_current <= 0.0:
202  rospy.logfatal('Maximum current for left motor (left_motor_max_current) must be greater than 0')
203  fatal_misconfiguration = True
204 
205  if self._right_motor_max_current <= 0.0:
206  rospy.logfatal('Maximum current for right motor (right_motor_max_current) must be greater than 0')
207  fatal_misconfiguration = True
208 
209  if self._active_brake_timeout <= 0:
210  rospy.logwarn('Active breaking timeout (active_brake_timeout) is disabled. If PID control is enabled and active breaking is disabled when idle, passive current may be applied motors causing potential motor overheating or reduced battery life.')
211 
212  if not isinstance(self._odom_frame, str) or not self._odom_frame:
213  rospy.logfatal('Invalid baselink frame: \'%s\'', self._odom_frame)
214  fatal_misconfiguration = True
215 
216  if not isinstance(self._base_link_frame, str) or not self._base_link_frame:
217  rospy.logfatal('Invalid baselink frame: \'%s\'', self._base_link_frame)
218  fatal_misconfiguration = True
219 
220  if self._wheel_base <= 0.0:
221  rospy.logfatal("Wheel base (wheel_base) must be greater than 0")
222  fatal_misconfiguration = True
223 
224  if self._wheel_radius <= 0.0:
225  rospy.logfatal("Wheel radius (wheel_radius) must be greater than 0")
226  fatal_misconfiguration = True
227 
228  if fatal_misconfiguration:
229  rospy.signal_shutdown('Fatal Misconfiguration')
230  exit(1)
231 
232  def _twist_cmd_cb(self, cmd):
233  self._last_cmd_vel_received = rospy.Time.now()
234  if self._estop_on_:
235  self._left_motor_speed = 0.0
236  self._right_motor_speed = 0.0
237  else:
239  self._twist_to_wheel_velocities(cmd.linear.x, cmd.angular.z)
240 
241  def _estop_enable_cb(self, estopstate):
242  if estopstate.data and not self._estop_on_:
243  self._variable_lock.acquire()
244  self._estop_on_ = True
245  self._variable_lock.release()
246 
247  def _estop_reset_cb(self, estopstate):
248  if estopstate.data and self._estop_on_:
249  self._variable_lock.acquire()
250  self._estop_on_ = False
251  self._variable_lock.release()
252 
253  def _twist_to_wheel_velocities(self, linear_rate, angular_rate):
254  if linear_rate > self._max_vel:
255  linear_rate = self._max_vel
256  if angular_rate > self._max_turn_rate:
257  angular_rate = self._max_turn_rate
258 
259  left_ = (linear_rate - 0.5 * self._wheel_base * angular_rate)
260  right_ = (linear_rate + 0.5 * self._wheel_base * angular_rate)
261 
262  return left_, right_
263 
264  def _motor_cmd_cb(self, event):
265  self._variable_lock.acquire()
266  left_speed, right_speed = self._left_motor_speed, self._right_motor_speed
267  self._variable_lock.release()
268 
270  if left_speed != 0.0 or right_speed != 0.0:
271  self._active_brake_enabled = False
272  self._active_brake_start_time = None
273  else:
274  if self._active_brake_enabled is False and self._active_brake_start_time is None:
275  self._active_brake_enabled = True
276  self._active_brake_start_time = rospy.Time.now()
277 
278  if (self._active_brake_enabled and self._active_brake_start_time is not None and
279  rospy.Time.now().to_sec() - self._active_brake_start_time.to_sec() > self._active_brake_timeout):
280  self.send_motor_duty(0, 0)
281  return
282 
283  left_cmd, right_cmd = self.speed_to_pulse_rate(left_speed), self.speed_to_pulse_rate(right_speed)
284  self.send_motor_velocities(left_cmd, right_cmd)
285 
286  else:
287  left_cmd, right_cmd = self.speed_to_duty(left_speed), self.speed_to_duty(right_speed)
288  self.send_motor_duty(left_cmd, right_cmd)
289 
290  def speed_to_pulse_rate(self, speed):
291  pulse_rate = speed / self._distance_per_encoder_pulse
292  return int(pulse_rate)
293 
294  def speed_to_duty(self, speed):
295  duty = int(32768 * (self._duty_coef * speed))
296  if duty < -32768:
297  duty = -32768
298  if duty > 32767:
299  duty = 32767
300 
301  return duty
302 
303  def send_motor_duty(self, left_duty, right_duty):
304  self._serial_lock.acquire()
305  self._roboclaw.DutyM1M2(self._address, left_duty, right_duty)
306  self._serial_lock.release()
307 
308  def send_motor_velocities(self, left_velocity, right_velocity):
309  """The Roboclaw does not have a serial timeout functionality. To make the robot safer we use the velocity with distance command to limit the distance the robot travels if the controller loses connection to the driver node"""
310  dt = 5 / self._motor_cmd_frequency
311  dt = 2.0 if dt > 2.0 else dt
312  self._serial_lock.acquire()
313  self._roboclaw.SpeedDistanceM1M2(self._address, left_velocity, int(dt * left_velocity),
314  right_velocity, int(dt * right_velocity), 1)
315  self._serial_lock.release()
316 
317  def _cmd_vel_timeout_cb(self, event):
318  now = rospy.get_rostime()
319  if now.to_sec() - self._last_cmd_vel_received.to_sec() > self._cmd_vel_timeout:
320  self._variable_lock.acquire()
321  self._left_motor_speed, self._right_motor_speed = 0.0, 0.0
322  self._variable_lock.release()
323 
324  def _odom_cb(self, msg):
325  self._serial_lock.acquire()
326  left_speed = self._distance_per_encoder_pulse * self._roboclaw.ReadISpeedM1(self._address)[1]
327  right_speed = self._distance_per_encoder_pulse * self._roboclaw.ReadISpeedM2(self._address)[1]
328  self._serial_lock.release()
329 
330  update_time = rospy.Time.now()
331  dt = (update_time - self._last_odom_update).to_sec()
332  self._last_odom_update = update_time
333 
334  linear_vel = 0.5 * (left_speed + right_speed)
335  turn_vel = right_speed - left_speed
336  alpha_dot = turn_vel / self._wheel_base
337 
338  self._odom_position_x += dt * math.cos(self._odom_orientation_theta) * linear_vel
339  self._odom_position_y += dt * math.sin(self._odom_orientation_theta) * linear_vel
340  self._odom_orientation_theta += dt * alpha_dot
341 
342  odom_msg = Odometry()
343  odom_msg.child_frame_id = self._base_link_frame
344  odom_msg.header.frame_id = self._odom_frame
345  odom_msg.header.stamp = rospy.Time.now()
346 
347  quat = PyKDL.Rotation.RPY(0, 0, self._odom_orientation_theta).GetQuaternion()
348 
349  odom_msg.pose.pose.position.x = self._odom_position_x
350  odom_msg.pose.pose.position.y = self._odom_position_y
351  odom_msg.pose.pose.orientation.x = quat[0]
352  odom_msg.pose.pose.orientation.y = quat[1]
353  odom_msg.pose.pose.orientation.z = quat[2]
354  odom_msg.pose.pose.orientation.w = quat[3]
355  odom_msg.pose.covariance[0] = 1e-2
356  odom_msg.pose.covariance[7] = 1e-2
357  odom_msg.pose.covariance[35] = 1e-2
358 
359  odom_msg.twist.twist.linear.x = linear_vel
360  odom_msg.twist.twist.angular.z = alpha_dot
361  odom_msg.twist.covariance[0] = 1e-3
362  odom_msg.twist.covariance[35] = 1e-4
363  self._pub_odom.publish(odom_msg)
364 
365  def _diag_cb(self, event):
366  # rospy callbacks are not thread safe. This prevents serial interference
367  self._diagnostics_update()
368  darr = DiagnosticArray()
369  darr.header.stamp = rospy.get_rostime()
370  darr.status = [
371  DiagnosticStatus(name="Roboclaw Driver",
372  message="OK",
373  hardware_id="none",
374  values=[KeyValue(key='Firmware Version', value=str(self._firmware_version)),
375  KeyValue(key='Left Motor Max Current', value='{MAX_CURRENT} A'.format(MAX_CURRENT=str(self._left_motor_max_current))),
376  KeyValue(key='Left Motor Current', value='{CURRENT} A'.format(CURRENT=str(self._left_motor_current))),
377  KeyValue(key='Left Motor Max Current', value='{MAX_CURRENT} A'.format(MAX_CURRENT=str(self._right_motor_max_current))),
378  KeyValue(key='Right Motor Current', value='{CURRENT} A'.format(CURRENT=str(self._right_motor_current))),
379  KeyValue(key='Battery Voltage', value='{VOLTAGE}V'.format(VOLTAGE=str(self._battery_voltage))),
380  KeyValue(key='Drive Mode', value='Closed Loop Control' if self._esc_feedback_controls_enabled else 'Open Loop Control')
381  ]
382  )
383  ]
384 
385  self._pub_diag.publish(darr)
386 
388  self.get_battery_voltage()
389  self.get_motor_current()
390 
392  self._serial_lock.acquire()
393  self._battery_voltage = self._roboclaw.ReadMainBatteryVoltage(self._address)[1] / 10.0
394  self._serial_lock.release()
395 
396  def get_motor_current(self):
397  self._serial_lock.acquire()
398  (res, m1_current, m2_current) = self._roboclaw.ReadCurrents(self._address)
399  self._serial_lock.release()
400 
401  if res:
402  self._left_motor_current = m1_current / 100.0
403  self._right_motor_current = m2_current / 100.0
404 
405  def spin(self):
406  rospy.spin()
407 
408 
409 if __name__ == '__main__':
411  rz.spin()
def _cmd_vel_timeout_cb(self, event)
Definition: rover_zero.py:317
def _configure_motor_controller(self)
Definition: rover_zero.py:108
def _motor_cmd_cb(self, event)
Definition: rover_zero.py:264
def send_motor_duty(self, left_duty, right_duty)
Definition: rover_zero.py:303
def verify_ros_parameters(self)
Definition: rover_zero.py:141
def _estop_enable_cb(self, estopstate)
Definition: rover_zero.py:241
def get_battery_voltage(self)
Definition: rover_zero.py:391
def _twist_cmd_cb(self, cmd)
Definition: rover_zero.py:232
def _diag_cb(self, event)
Definition: rover_zero.py:365
def _diagnostics_update(self)
Definition: rover_zero.py:387
def send_motor_velocities(self, left_velocity, right_velocity)
Definition: rover_zero.py:308
def _estop_reset_cb(self, estopstate)
Definition: rover_zero.py:247
def _twist_to_wheel_velocities(self, linear_rate, angular_rate)
Definition: rover_zero.py:253
def speed_to_pulse_rate(self, speed)
Definition: rover_zero.py:290
def _odom_cb(self, msg)
Definition: rover_zero.py:324
def speed_to_duty(self, speed)
Definition: rover_zero.py:294


rr_rover_zero_driver
Author(s):
autogenerated on Thu Sep 10 2020 03:38:42