2 import roslib; roslib.load_manifest(
'xsens_driver')
10 from std_msgs.msg
import Header, String, UInt16
11 from sensor_msgs.msg
import Imu, NavSatFix, NavSatStatus, MagneticField,\
12 FluidPressure, Temperature, TimeReference
13 from geometry_msgs.msg
import TwistStamped, PointStamped
14 from diagnostic_msgs.msg
import DiagnosticArray, DiagnosticStatus
21 from math
import radians, sqrt, atan2
22 from tf.transformations
import quaternion_from_matrix, quaternion_from_euler,\
28 v = rospy.get_param(name)
29 rospy.loginfo(
"Found parameter: %s, value: %s" % (name, str(v)))
32 rospy.logwarn(
"Cannot find value for parameter: %s, assigning " 33 "default: %s" % (name, str(v)))
39 if len(default) != len(value):
40 rospy.logfatal(
"Parameter %s should be a list of size %d", name, len(default))
49 matrix[i*n + i] = diagonal[i]
59 initial_wait =
get_param(
'~initial_wait', 0.1)
62 initial_wait=initial_wait)
64 device, baudrate = devs[0]
65 rospy.loginfo(
"Detected MT device on port %s @ %d bps" 68 rospy.logerr(
"Fatal: could not find proper MT device.")
69 rospy.signal_shutdown(
"Could not find proper MT device.")
73 initial_wait=initial_wait)
75 rospy.logerr(
"Fatal: could not find proper baudrate.")
76 rospy.signal_shutdown(
"Could not find proper baudrate.")
79 rospy.loginfo(
"MT node interface: %s at %d bd." % (device, baudrate))
81 initial_wait=initial_wait)
85 no_rotation_duration =
get_param(
'~no_rotation_duration', 0)
86 if no_rotation_duration:
87 rospy.loginfo(
"Starting the no-rotation procedure to estimate the " 88 "gyroscope biases for %d s. Please don't move the IMU" 89 " during this time." % no_rotation_duration)
90 self.mt.SetNoRotation(no_rotation_duration)
97 get_param_list(
'~angular_velocity_covariance_diagonal', [radians(0.025)] * 3)
100 get_param_list(
'~linear_acceleration_covariance_diagonal', [0.0004] * 3)
103 get_param_list(
"~orientation_covariance_diagonal", [radians(1.), radians(1.), radians(9.)])
107 self.
stest_stat = DiagnosticStatus(name=
'mtnode: Self Test', level=1,
108 message=
'No status information')
109 self.
xkf_stat = DiagnosticStatus(name=
'mtnode: XKF Valid', level=1,
110 message=
'No status information')
111 self.
gps_stat = DiagnosticStatus(name=
'mtnode: GPS Fix', level=1,
112 message=
'No status information')
132 self.
str_pub = rospy.Publisher(
'imu_data_str', String, queue_size=10)
138 self.imu_msg.orientation_covariance = (-1., )*9
139 self.imu_msg.angular_velocity_covariance = (-1., )*9
140 self.imu_msg.linear_acceleration_covariance = (-1., )*9
149 self.mag_msg.magnetic_field_covariance = (0, )*9
152 self.temp_msg.variance = 0.
155 self.press_msg.variance = 0.
167 while not rospy.is_shutdown():
172 except (select.error, OSError, serial.serialutil.SerialException):
176 '''Read data from device and publishes ROS messages.''' 177 def convert_coords(x, y, z, source, dest=self.frame_local):
178 """Convert the coordinates between ENU, NED, and NWU.""" 184 elif source ==
'NWU':
193 def convert_quat(q, source, dest=self.frame_local):
194 """Convert a quaternion between ENU, NED, and NWU.""" 195 def q_mult((w0, x0, y0, z0), (w1, x1, y1, z1)):
196 """Quaternion multiplication.""" 197 w = w0*w1 - x0*x1 - y0*y1 - z0*z1
198 x = w0*x1 + x0*w1 + y0*z1 - z0*y1
199 y = w0*y1 - x0*z1 + y0*w1 + z0*x1
200 z = w0*z1 + x0*y1 - y0*x1 + z0*w1
202 q_enu_ned = (0, 1./sqrt(2), 1./sqrt(2), 0)
203 q_enu_nwu = (1./sqrt(2), 0, 0, -1./sqrt(2))
204 q_ned_nwu = (0, -1, 0, 0)
205 q_ned_enu = (0, -1./sqrt(2), -1./sqrt(2), 0)
206 q_nwu_enu = (1./sqrt(2), 0, 0, 1./sqrt(2))
207 q_nwu_ned = (0, 1, 0, 0)
212 return q_mult(q_enu_ned, q)
214 return q_mult(q_enu_nwu, q)
215 elif source ==
'NED':
217 return q_mult(q_ned_enu, q)
221 return q_mult(q_ned_nwu, q)
222 elif source ==
'NWU':
224 return q_mult(q_nwu_enu, q)
226 return q_mult(q_nwu_ned, q)
230 def publish_time_ref(secs, nsecs, source):
231 """Publish a time reference.""" 236 'time_reference', TimeReference, queue_size=10)
237 time_ref_msg = TimeReference()
238 time_ref_msg.header = self.
h 239 time_ref_msg.time_ref.secs = secs
240 time_ref_msg.time_ref.nsecs = nsecs
241 time_ref_msg.source = source
242 self.time_ref_pub.publish(time_ref_msg)
244 def stamp_from_itow(itow, y=None, m=None, d=None, ns=0, week=None):
245 """Return (secs, nsecs) from GPS time of week ms information.""" 247 stamp_day = datetime.datetime(y, m, d)
248 elif week
is not None:
249 epoch = datetime.datetime(1980, 1, 6)
250 stamp_day = epoch + datetime.timedelta(weeks=week)
252 today = datetime.date.today()
253 stamp_day = datetime.datetime(today.year, today.month,
255 iso_day = stamp_day.isoweekday()
257 start_of_week = stamp_day - datetime.timedelta(days=iso_day)
259 stamp_ms = start_of_week + datetime.timedelta(milliseconds=itow)
260 secs = calendar.timegm((stamp_ms.year, stamp_ms.month, stamp_ms.day,
261 stamp_ms.hour, stamp_ms.minute,
262 stamp_ms.second, 0, 0, -1))
263 nsecs = stamp_ms.microsecond * 1000 + ns
270 def fill_from_RAW(raw_data):
271 '''Fill messages with information from 'raw' MTData block.''' 274 rospy.loginfo(
"Got MTi data packet: 'RAW', ignored!")
276 def fill_from_RAWGPS(rawgps_data):
277 '''Fill messages with information from 'rawgps' MTData block.''' 278 if rawgps_data[
'bGPS'] < self.
old_bGPS:
281 self.raw_gps_msg.latitude = rawgps_data[
'LAT']*1e-7
282 self.raw_gps_msg.longitude = rawgps_data[
'LON']*1e-7
283 self.raw_gps_msg.altitude = rawgps_data[
'ALT']*1e-3
287 def fill_from_Temp(temp):
288 '''Fill messages with information from 'temperature' MTData block. 291 self.temp_msg.temperature = temp
293 def fill_from_Calib(imu_data):
294 '''Fill messages with information from 'calibrated' MTData block.''' 297 x, y, z = imu_data[
'gyrX'], imu_data[
'gyrY'], imu_data[
'gyrZ']
298 self.imu_msg.angular_velocity.x = x
299 self.imu_msg.angular_velocity.y = y
300 self.imu_msg.angular_velocity.z = z
303 self.vel_msg.twist.angular.x = x
304 self.vel_msg.twist.angular.y = y
305 self.vel_msg.twist.angular.z = z
310 x, y, z = imu_data[
'accX'], imu_data[
'accY'], imu_data[
'accZ']
311 self.imu_msg.linear_acceleration.x = x
312 self.imu_msg.linear_acceleration.y = y
313 self.imu_msg.linear_acceleration.z = z
319 x, y, z = imu_data[
'magX'], imu_data[
'magY'], imu_data[
'magZ']
320 self.mag_msg.magnetic_field.x = x
321 self.mag_msg.magnetic_field.y = y
322 self.mag_msg.magnetic_field.z = z
326 def fill_from_Orient(orient_data):
327 '''Fill messages with information from 'orientation' MTData block. 330 if 'quaternion' in orient_data:
331 w, x, y, z = orient_data[
'quaternion']
332 elif 'roll' in orient_data:
333 x, y, z, w = quaternion_from_euler(
334 radians(orient_data[
'roll']), radians(orient_data[
'pitch']),
335 radians(orient_data[
'yaw']))
336 elif 'matrix' in orient_data:
337 m = identity_matrix()
338 m[:3, :3] = orient_data[
'matrix']
339 x, y, z, w = quaternion_from_matrix(m)
340 w, x, y, z = convert_quat((w, x, y, z), o[
'frame'])
341 self.imu_msg.orientation.x = x
342 self.imu_msg.orientation.y = y
343 self.imu_msg.orientation.z = z
344 self.imu_msg.orientation.w = w
347 def fill_from_Auxiliary(aux_data):
348 '''Fill messages with information from 'Auxiliary' MTData block.''' 350 self.anin1_msg.data = o[
'Ain_1']
355 self.anin2_msg.data = o[
'Ain_2']
360 def fill_from_Pos(position_data):
361 '''Fill messages with information from 'position' MTData block.''' 363 self.pos_gps_msg.latitude = position_data[
'Lat']
364 self.pos_gps_msg.longitude = position_data[
'Lon']
365 self.pos_gps_msg.altitude = position_data[
'Alt']
367 def fill_from_Vel(velocity_data):
368 '''Fill messages with information from 'velocity' MTData block.''' 370 x, y, z = convert_coords(
371 velocity_data[
'Vel_X'], velocity_data[
'Vel_Y'],
372 velocity_data[
'Vel_Z'], o[
'frame'])
373 self.vel_msg.twist.linear.x = x
374 self.vel_msg.twist.linear.y = y
375 self.vel_msg.twist.linear.z = z
377 def fill_from_Stat(status):
378 '''Fill messages with information from 'status' MTData block.''' 381 self.stest_stat.level = DiagnosticStatus.OK
382 self.stest_stat.message =
"Ok" 384 self.stest_stat.level = DiagnosticStatus.ERROR
385 self.stest_stat.message =
"Failed" 387 self.xkf_stat.level = DiagnosticStatus.OK
388 self.xkf_stat.message =
"Valid" 390 self.xkf_stat.level = DiagnosticStatus.WARN
391 self.xkf_stat.message =
"Invalid" 393 self.gps_stat.level = DiagnosticStatus.OK
394 self.gps_stat.message =
"Ok" 395 self.raw_gps_msg.status.status = NavSatStatus.STATUS_FIX
396 self.raw_gps_msg.status.service = NavSatStatus.SERVICE_GPS
398 self.pos_gps_msg.status.status = NavSatStatus.STATUS_FIX
399 self.pos_gps_msg.status.service = NavSatStatus.SERVICE_GPS
401 self.gps_stat.level = DiagnosticStatus.WARN
402 self.gps_stat.message =
"No fix" 403 self.raw_gps_msg.status.status = NavSatStatus.STATUS_NO_FIX
404 self.raw_gps_msg.status.service = 0
406 self.pos_gps_msg.status.status = NavSatStatus.STATUS_NO_FIX
407 self.pos_gps_msg.status.service = 0
409 def fill_from_Sample(ts):
410 '''Catch 'Sample' MTData blocks.''' 414 def fill_from_Temperature(o):
415 '''Fill messages with information from 'Temperature' MTData2 block. 418 self.temp_msg.temperature = o[
'Temp']
420 def fill_from_Timestamp(o):
421 '''Fill messages with information from 'Timestamp' MTData2 block.''' 424 y, m, d, hr, mi, s, ns, f = o[
'Year'], o[
'Month'], o[
'Day'],\
425 o[
'Hour'], o[
'Minute'], o[
'Second'], o[
'ns'], o[
'Flags']
427 secs = calendar.timegm((y, m, d, hr, mi, s, 0, 0, 0))
428 publish_time_ref(secs, ns,
'UTC time')
432 itow = o[
'TimeOfWeek']
433 secs, nsecs = stamp_from_itow(itow)
434 publish_time_ref(secs, nsecs,
'integer time of week')
438 sample_time_fine = o[
'SampleTimeFine']
440 secs = int(sample_time_fine / 10000)
441 nsecs = 1e5 * (sample_time_fine % 10000)
442 publish_time_ref(secs, nsecs,
'sample time fine')
446 sample_time_coarse = o[
'SampleTimeCoarse']
447 publish_time_ref(sample_time_coarse, 0,
'sample time coarse')
453 def fill_from_Orientation_Data(o):
454 '''Fill messages with information from 'Orientation Data' MTData2 458 x, y, z, w = o[
'Q1'], o[
'Q2'], o[
'Q3'], o[
'Q0']
462 x, y, z, w = quaternion_from_euler(radians(o[
'Roll']),
468 a, b, c, d, e, f, g, h, i = o[
'a'], o[
'b'], o[
'c'], o[
'd'],\
469 o[
'e'], o[
'f'], o[
'g'], o[
'h'], o[
'i']
470 m = identity_matrix()
471 m[:3, :3] = ((a, b, c), (d, e, f), (g, h, i))
472 x, y, z, w = quaternion_from_matrix(m)
475 w, x, y, z = convert_quat((w, x, y, z), o[
'frame'])
476 self.imu_msg.orientation.x = x
477 self.imu_msg.orientation.y = y
478 self.imu_msg.orientation.z = z
479 self.imu_msg.orientation.w = w
482 def fill_from_Pressure(o):
483 '''Fill messages with information from 'Pressure' MTData2 block.''' 484 self.press_msg.fluid_pressure = o[
'Pressure']
487 def fill_from_Acceleration(o):
488 '''Fill messages with information from 'Acceleration' MTData2 494 x, y, z = o[
'Delta v.x'], o[
'Delta v.y'], o[
'Delta v.z']
498 x, y, z = o[
'freeAccX'], o[
'freeAccY'], o[
'freeAccZ']
502 x, y, z = o[
'accX'], o[
'accY'], o[
'accZ']
505 self.imu_msg.linear_acceleration.x = x
506 self.imu_msg.linear_acceleration.y = y
507 self.imu_msg.linear_acceleration.z = z
510 def fill_from_Position(o):
511 '''Fill messages with information from 'Position' MTData2 block.''' 513 self.pos_gps_msg.latitude = o[
'lat']
514 self.pos_gps_msg.longitude = o[
'lon']
516 alt = o.get(
'altEllipsoid', o.get(
'altMsl', 0))
517 self.pos_gps_msg.altitude = alt
522 x, y, z = o[
'ecefX'], o[
'ecefY'], o[
'ecefZ']
524 self.ecef_msg.point.x = x
525 self.ecef_msg.point.y = y
526 self.ecef_msg.point.z = z
531 def fill_from_GNSS(o):
532 '''Fill messages with information from 'GNSS' MTData2 block.''' 535 itow, y, m, d, ns, f = o[
'itow'], o[
'year'], o[
'month'],\
536 o[
'day'], o[
'nano'], o[
'valid']
538 secs, nsecs = stamp_from_itow(itow, y, m, d, ns)
539 publish_time_ref(secs, nsecs,
'GNSS time UTC')
541 fixtype = o[
'fixtype']
543 self.raw_gps_msg.status.status = NavSatStatus.STATUS_NO_FIX
544 self.raw_gps_msg.status.service = 0
546 self.raw_gps_msg.status.status = NavSatStatus.STATUS_FIX
547 self.raw_gps_msg.status.service = NavSatStatus.SERVICE_GPS
549 self.raw_gps_msg.latitude = o[
'lat']
550 self.raw_gps_msg.longitude = o[
'lon']
551 self.raw_gps_msg.altitude = o[
'height']/1e3
561 def fill_from_Angular_Velocity(o):
562 '''Fill messages with information from 'Angular Velocity' MTData2 565 dqw, dqx, dqy, dqz = (o[
'Delta q0'], o[
'Delta q1'],
566 o[
'Delta q2'], o[
'Delta q3'])
567 now = rospy.Time.now()
584 ca_2, sa_2 = dqw, sqrt(dqx**2 + dqy**2 + dqz**2)
585 ca = ca_2**2 - sa_2**2
587 rotation_angle = atan2(sa, ca)
590 f = rotation_speed / sa_2
591 x, y, z = f*dqx, f*dqy, f*dqz
592 self.imu_msg.angular_velocity.x = x
593 self.imu_msg.angular_velocity.y = y
594 self.imu_msg.angular_velocity.z = z
597 self.vel_msg.twist.angular.x = x
598 self.vel_msg.twist.angular.y = y
599 self.vel_msg.twist.angular.z = z
604 x, y, z = o[
'gyrX'], o[
'gyrY'], o[
'gyrZ']
605 self.imu_msg.angular_velocity.x = x
606 self.imu_msg.angular_velocity.y = y
607 self.imu_msg.angular_velocity.z = z
610 self.vel_msg.twist.angular.x = x
611 self.vel_msg.twist.angular.y = y
612 self.vel_msg.twist.angular.z = z
617 def fill_from_GPS(o):
618 '''Fill messages with information from 'GPS' MTData2 block.''' 621 x, y, z = o[
'ecefX'], o[
'ecefY'], o[
'ecefZ']
622 self.ecef_msg.point.x = x * 0.01
623 self.ecef_msg.point.y = y * 0.01
624 self.ecef_msg.point.z = z * 0.01
626 vx, vy, vz = o[
'ecefVX'], o[
'ecefVY'], o[
'ecefVZ']
627 self.vel_msg.twist.linear.x = vx * 0.01
628 self.vel_msg.twist.linear.y = vy * 0.01
629 self.vel_msg.twist.linear.z = vz * 0.01
631 itow, ns, week, f = o[
'iTOW'], o[
'fTOW'], o[
'Week'], o[
'Flags']
632 if (f & 0x0C) == 0xC:
633 secs, nsecs = stamp_from_itow(itow, ns=ns, week=week)
634 publish_time_ref(secs, nsecs,
'GPS Time')
640 itow, y, m, d, ns, f = o[
'iTOW'], o[
'year'], o[
'month'],\
641 o[
'day'], o[
'nano'], o[
'valid']
643 secs, nsecs = stamp_from_itow(itow, y, m, d, ns)
644 publish_time_ref(secs, nsecs,
'GPS Time UTC')
649 def fill_from_SCR(o):
650 '''Fill messages with information from 'SCR' MTData2 block.''' 654 def fill_from_Analog_In(o):
655 '''Fill messages with information from 'Analog In' MTData2 block.''' 657 self.anin1_msg.data = o[
'analogIn1']
658 self.pub_anin1 =
True 662 self.anin2_msg.data = o[
'analogIn2']
663 self.pub_anin2 =
True 667 def fill_from_Magnetic(o):
668 '''Fill messages with information from 'Magnetic' MTData2 block.''' 669 x, y, z = o[
'magX'], o[
'magY'], o[
'magZ']
670 self.mag_msg.magnetic_field.x = x
671 self.mag_msg.magnetic_field.y = y
672 self.mag_msg.magnetic_field.z = z
675 def fill_from_Velocity(o):
676 '''Fill messages with information from 'Velocity' MTData2 block.''' 677 x, y, z = convert_coords(o[
'velX'], o[
'velY'], o[
'velZ'],
679 self.vel_msg.twist.linear.x = x
680 self.vel_msg.twist.linear.y = y
681 self.vel_msg.twist.linear.z = z
684 def fill_from_Status(o):
685 '''Fill messages with information from 'Status' MTData2 block.''' 687 status = o[
'StatusByte']
688 fill_from_Stat(status)
692 status = o[
'StatusWord']
693 fill_from_Stat(status)
697 def find_handler_name(name):
698 return "fill_from_%s" % (name.replace(
" ",
"_"))
702 data = self.mt.read_measurement()
708 self.h.stamp = rospy.Time.now()
715 for n, o
in data.items():
717 locals()[find_handler_name(n)](o)
719 rospy.logwarn(
"Unknown MTi data packet: '%s', ignoring." % n)
723 self.imu_msg.header = self.
h 725 self.
imu_pub = rospy.Publisher(
'imu/data', Imu, queue_size=10)
726 self.imu_pub.publish(self.
imu_msg)
728 self.raw_gps_msg.header = self.
h 730 self.
raw_gps_pub = rospy.Publisher(
'raw_fix', NavSatFix, queue_size=10)
733 self.pos_gps_msg.header = self.
h 735 self.
pos_gps_pub = rospy.Publisher(
'fix', NavSatFix, queue_size=10)
738 self.vel_msg.header = self.
h 740 self.
vel_pub = rospy.Publisher(
'velocity', TwistStamped,
742 self.vel_pub.publish(self.
vel_msg)
744 self.mag_msg.header = self.
h 746 self.
mag_pub = rospy.Publisher(
'imu/mag', MagneticField,
748 self.mag_pub.publish(self.
mag_msg)
750 self.temp_msg.header = self.
h 752 self.
temp_pub = rospy.Publisher(
'temperature', Temperature,
754 self.temp_pub.publish(self.
temp_msg)
756 self.press_msg.header = self.
h 758 self.
press_pub = rospy.Publisher(
'pressure', FluidPressure,
764 UInt16, queue_size=10)
765 self.analog_in1_pub.publish(self.
anin1_msg)
770 self.analog_in2_pub.publish(self.
anin2_msg)
772 self.ecef_msg.header = self.
h 774 self.
ecef_pub = rospy.Publisher(
'ecef', PointStamped,
776 self.ecef_pub.publish(self.
ecef_msg)
778 self.diag_msg.header = self.
h 780 self.
diag_pub = rospy.Publisher(
'/diagnostics', DiagnosticArray,
782 self.diag_pub.publish(self.
diag_msg)
784 self.str_pub.publish(str(data))
788 '''Create a ROS node and instantiate the class.''' 789 rospy.init_node(
'xsens_driver')
794 if __name__ ==
'__main__':
def get_param_list(name, default)
def find_baudrate(port, timeout=0.002, verbose=False, initial_wait=0.1)
Auto detect baudrate.
def matrix_from_diagonal(diagonal)
angular_velocity_covariance
def get_param(name, default)
def find_devices(timeout=0.002, verbose=False, initial_wait=0.1)
linear_acceleration_covariance