6 from xsens_driver
import mtdevice
7 from xsens_driver
import mtdef
9 from std_msgs.msg
import Header, String, UInt16
10 from sensor_msgs.msg
import Imu, NavSatFix, NavSatStatus, MagneticField,\
11 FluidPressure, Temperature, TimeReference
12 from geometry_msgs.msg
import TwistStamped, PointStamped
13 from diagnostic_msgs.msg
import DiagnosticArray, DiagnosticStatus
20 from math
import radians, sqrt, atan2
21 from tf.transformations
import quaternion_from_matrix, quaternion_from_euler,\
27 v = rospy.get_param(name)
28 rospy.loginfo(
"Found parameter: %s, value: %s" % (name, str(v)))
31 rospy.logwarn(
"Cannot find value for parameter: %s, assigning " 32 "default: %s" % (name, str(v)))
38 if len(default) != len(value):
39 rospy.logfatal(
"Parameter %s should be a list of size %d", name, len(default))
48 matrix[i*n + i] = diagonal[i]
58 initial_wait =
get_param(
'~initial_wait', 0.1)
60 devs = mtdevice.find_devices(timeout=timeout,
61 initial_wait=initial_wait)
63 device, baudrate = devs[0]
64 rospy.loginfo(
"Detected MT device on port %s @ %d bps" 67 rospy.logerr(
"Fatal: could not find proper MT device.")
68 rospy.signal_shutdown(
"Could not find proper MT device.")
71 baudrate = mtdevice.find_baudrate(device, timeout=timeout,
72 initial_wait=initial_wait)
74 rospy.logerr(
"Fatal: could not find proper baudrate.")
75 rospy.signal_shutdown(
"Could not find proper baudrate.")
78 rospy.loginfo(
"MT node interface: %s at %d bd." % (device, baudrate))
80 initial_wait=initial_wait)
84 no_rotation_duration =
get_param(
'~no_rotation_duration', 0)
85 if no_rotation_duration:
86 rospy.loginfo(
"Starting the no-rotation procedure to estimate the " 87 "gyroscope biases for %d s. Please don't move the " 88 "IMU during this time." % no_rotation_duration)
89 self.
mt.SetNoRotation(no_rotation_duration)
96 get_param_list(
'~angular_velocity_covariance_diagonal', [radians(0.025)] * 3)
99 get_param_list(
'~linear_acceleration_covariance_diagonal', [0.0004] * 3)
102 get_param_list(
"~orientation_covariance_diagonal", [radians(1.), radians(1.), radians(9.)])
106 self.
stest_stat = DiagnosticStatus(name=
'mtnode: Self Test', level=1,
107 message=
'No status information')
108 self.
xkf_stat = DiagnosticStatus(name=
'mtnode: XKF Valid', level=1,
109 message=
'No status information')
110 self.
gps_stat = DiagnosticStatus(name=
'mtnode: GPS Fix', level=1,
111 message=
'No status information')
131 self.
str_pub = rospy.Publisher(
'imu_data_str', String, queue_size=10)
137 self.
imu_msg.orientation_covariance = (-1., )*9
138 self.
imu_msg.angular_velocity_covariance = (-1., )*9
139 self.
imu_msg.linear_acceleration_covariance = (-1., )*9
148 self.
mag_msg.magnetic_field_covariance = (0, )*9
166 while not rospy.is_shutdown():
171 except (select.error, OSError, serial.serialutil.SerialException):
175 '''Read data from device and publishes ROS messages.''' 176 def convert_coords(x, y, z, source, dest=self.frame_local):
177 """Convert the coordinates between ENU, NED, and NWU.""" 183 elif source ==
'NWU':
192 def convert_quat(q, source, dest=self.frame_local):
193 """Convert a quaternion between ENU, NED, and NWU.""" 195 """Quaternion multiplication.""" 198 w = w0*w1 - x0*x1 - y0*y1 - z0*z1
199 x = w0*x1 + x0*w1 + y0*z1 - z0*y1
200 y = w0*y1 - x0*z1 + y0*w1 + z0*x1
201 z = w0*z1 + x0*y1 - y0*x1 + z0*w1
203 q_enu_ned = (0, 1./sqrt(2), 1./sqrt(2), 0)
204 q_enu_nwu = (1./sqrt(2), 0, 0, -1./sqrt(2))
205 q_ned_nwu = (0, -1, 0, 0)
206 q_ned_enu = (0, -1./sqrt(2), -1./sqrt(2), 0)
207 q_nwu_enu = (1./sqrt(2), 0, 0, 1./sqrt(2))
208 q_nwu_ned = (0, 1, 0, 0)
213 return q_mult(q_enu_ned, q)
215 return q_mult(q_enu_nwu, q)
216 elif source ==
'NED':
218 return q_mult(q_ned_enu, q)
222 return q_mult(q_ned_nwu, q)
223 elif source ==
'NWU':
225 return q_mult(q_nwu_enu, q)
227 return q_mult(q_nwu_ned, q)
231 def publish_time_ref(secs, nsecs, source):
232 """Publish a time reference.""" 237 'time_reference', TimeReference, queue_size=10)
238 time_ref_msg = TimeReference()
239 time_ref_msg.header = self.
h 240 time_ref_msg.time_ref.secs = secs
241 time_ref_msg.time_ref.nsecs = nsecs
242 time_ref_msg.source = source
245 def stamp_from_itow(itow, y=None, m=None, d=None, ns=0, week=None):
246 """Return (secs, nsecs) from GPS time of week ms information.""" 248 stamp_day = datetime.datetime(y, m, d)
249 elif week
is not None:
250 epoch = datetime.datetime(1980, 1, 6)
251 stamp_day = epoch + datetime.timedelta(weeks=week)
253 today = datetime.date.today()
254 stamp_day = datetime.datetime(today.year, today.month,
256 iso_day = stamp_day.isoweekday()
258 start_of_week = stamp_day - datetime.timedelta(days=iso_day)
260 stamp_ms = start_of_week + datetime.timedelta(milliseconds=itow)
261 secs = calendar.timegm((
262 stamp_ms.year, stamp_ms.month, stamp_ms.day, stamp_ms.hour,
263 stamp_ms.minute, stamp_ms.second, 0, 0, -1))
264 nsecs = stamp_ms.microsecond * 1000 + ns
271 def fill_from_RAW(raw_data):
272 '''Fill messages with information from 'raw' MTData block.''' 275 rospy.loginfo(
"Got MTi data packet: 'RAW', ignored!")
277 def fill_from_RAWGPS(rawgps_data):
278 '''Fill messages with information from 'rawgps' MTData block.''' 279 if rawgps_data[
'bGPS'] < self.
old_bGPS:
282 self.
raw_gps_msg.latitude = rawgps_data[
'LAT']*1e-7
283 self.
raw_gps_msg.longitude = rawgps_data[
'LON']*1e-7
284 self.
raw_gps_msg.altitude = rawgps_data[
'ALT']*1e-3
288 def fill_from_Temp(temp):
289 '''Fill messages with information from 'temperature' MTData block. 294 def fill_from_Calib(imu_data):
295 '''Fill messages with information from 'calibrated' MTData block. 299 x, y, z = imu_data[
'gyrX'], imu_data[
'gyrY'], imu_data[
'gyrZ']
300 self.
imu_msg.angular_velocity.x = x
301 self.
imu_msg.angular_velocity.y = y
302 self.
imu_msg.angular_velocity.z = z
305 self.
vel_msg.twist.angular.x = x
306 self.
vel_msg.twist.angular.y = y
307 self.
vel_msg.twist.angular.z = z
312 x, y, z = imu_data[
'accX'], imu_data[
'accY'], imu_data[
'accZ']
313 self.
imu_msg.linear_acceleration.x = x
314 self.
imu_msg.linear_acceleration.y = y
315 self.
imu_msg.linear_acceleration.z = z
321 x, y, z = imu_data[
'magX'], imu_data[
'magY'], imu_data[
'magZ']
322 self.
mag_msg.magnetic_field.x = x
323 self.
mag_msg.magnetic_field.y = y
324 self.
mag_msg.magnetic_field.z = z
328 def fill_from_Orient(orient_data):
329 '''Fill messages with information from 'orientation' MTData block. 332 if 'quaternion' in orient_data:
333 w, x, y, z = orient_data[
'quaternion']
334 elif 'roll' in orient_data:
335 x, y, z, w = quaternion_from_euler(
336 radians(orient_data[
'roll']),
337 radians(orient_data[
'pitch']),
338 radians(orient_data[
'yaw']))
339 elif 'matrix' in orient_data:
340 m = identity_matrix()
341 m[:3, :3] = orient_data[
'matrix']
342 x, y, z, w = quaternion_from_matrix(m)
343 w, x, y, z = convert_quat((w, x, y, z), o[
'frame'])
350 def fill_from_Auxiliary(aux_data):
351 '''Fill messages with information from 'Auxiliary' MTData block.''' 363 def fill_from_Pos(position_data):
364 '''Fill messages with information from 'position' MTData block.''' 370 def fill_from_Vel(velocity_data):
371 '''Fill messages with information from 'velocity' MTData block.''' 373 x, y, z = convert_coords(
374 velocity_data[
'Vel_X'], velocity_data[
'Vel_Y'],
375 velocity_data[
'Vel_Z'], o[
'frame'])
376 self.
vel_msg.twist.linear.x = x
377 self.
vel_msg.twist.linear.y = y
378 self.
vel_msg.twist.linear.z = z
380 def fill_from_Stat(status):
381 '''Fill messages with information from 'status' MTData block.''' 387 self.
stest_stat.level = DiagnosticStatus.ERROR
390 self.
xkf_stat.level = DiagnosticStatus.OK
393 self.
xkf_stat.level = DiagnosticStatus.WARN
396 self.
gps_stat.level = DiagnosticStatus.OK
398 self.
raw_gps_msg.status.status = NavSatStatus.STATUS_FIX
399 self.
raw_gps_msg.status.service = NavSatStatus.SERVICE_GPS
401 self.
pos_gps_msg.status.status = NavSatStatus.STATUS_FIX
402 self.
pos_gps_msg.status.service = NavSatStatus.SERVICE_GPS
404 self.
gps_stat.level = DiagnosticStatus.WARN
406 self.
raw_gps_msg.status.status = NavSatStatus.STATUS_NO_FIX
409 self.
pos_gps_msg.status.status = NavSatStatus.STATUS_NO_FIX
412 def fill_from_Sample(ts):
413 '''Catch 'Sample' MTData blocks.''' 417 def fill_from_Temperature(o):
418 '''Fill messages with information from 'Temperature' MTData2 block. 421 self.
temp_msg.temperature = o[
'Temp']
423 def fill_from_Timestamp(o):
424 '''Fill messages with information from 'Timestamp' MTData2 block. 428 y, m, d, hr, mi, s, ns, f = o[
'Year'], o[
'Month'], o[
'Day'],\
429 o[
'Hour'], o[
'Minute'], o[
'Second'], o[
'ns'], o[
'Flags']
431 secs = calendar.timegm((y, m, d, hr, mi, s, 0, 0, 0))
432 publish_time_ref(secs, ns,
'UTC time')
436 itow = o[
'TimeOfWeek']
437 secs, nsecs = stamp_from_itow(itow)
438 publish_time_ref(secs, nsecs,
'integer time of week')
442 sample_time_fine = o[
'SampleTimeFine']
444 secs = int(sample_time_fine / 10000)
445 nsecs = 1e5 * (sample_time_fine % 10000)
446 publish_time_ref(secs, nsecs,
'sample time fine')
450 sample_time_coarse = o[
'SampleTimeCoarse']
451 publish_time_ref(sample_time_coarse, 0,
'sample time coarse')
457 def fill_from_Orientation_Data(o):
458 '''Fill messages with information from 'Orientation Data' MTData2 462 x, y, z, w = o[
'Q1'], o[
'Q2'], o[
'Q3'], o[
'Q0']
466 x, y, z, w = quaternion_from_euler(radians(o[
'Roll']),
472 a, b, c, d, e, f, g, h, i = o[
'a'], o[
'b'], o[
'c'], o[
'd'],\
473 o[
'e'], o[
'f'], o[
'g'], o[
'h'], o[
'i']
474 m = identity_matrix()
475 m[:3, :3] = ((a, b, c), (d, e, f), (g, h, i))
476 x, y, z, w = quaternion_from_matrix(m)
479 w, x, y, z = convert_quat((w, x, y, z), o[
'frame'])
486 def fill_from_Pressure(o):
487 '''Fill messages with information from 'Pressure' MTData2 block.''' 488 self.
press_msg.fluid_pressure = o[
'Pressure']
491 def fill_from_Acceleration(o):
492 '''Fill messages with information from 'Acceleration' MTData2 498 x, y, z = o[
'Delta v.x'], o[
'Delta v.y'], o[
'Delta v.z']
502 x, y, z = o[
'freeAccX'], o[
'freeAccY'], o[
'freeAccZ']
506 x, y, z = o[
'accX'], o[
'accY'], o[
'accZ']
509 self.
imu_msg.linear_acceleration.x = x
510 self.
imu_msg.linear_acceleration.y = y
511 self.
imu_msg.linear_acceleration.z = z
514 def fill_from_Position(o):
515 '''Fill messages with information from 'Position' MTData2 block.''' 520 alt = o.get(
'altEllipsoid', o.get(
'altMsl', 0))
526 x, y, z = o[
'ecefX'], o[
'ecefY'], o[
'ecefZ']
535 def fill_from_GNSS(o):
536 '''Fill messages with information from 'GNSS' MTData2 block.''' 539 itow, y, m, d, ns, f = o[
'itow'], o[
'year'], o[
'month'],\
540 o[
'day'], o[
'nano'], o[
'valid']
542 secs, nsecs = stamp_from_itow(itow, y, m, d, ns)
543 publish_time_ref(secs, nsecs,
'GNSS time UTC')
545 fixtype = o[
'fixtype']
548 self.
raw_gps_msg.status.status = NavSatStatus.STATUS_NO_FIX
552 self.
raw_gps_msg.status.status = NavSatStatus.STATUS_FIX
553 self.
raw_gps_msg.status.service = NavSatStatus.SERVICE_GPS
567 def fill_from_Angular_Velocity(o):
568 '''Fill messages with information from 'Angular Velocity' MTData2 571 dqw, dqx, dqy, dqz = (o[
'Delta q0'], o[
'Delta q1'],
572 o[
'Delta q2'], o[
'Delta q3'])
573 now = rospy.Time.now()
590 ca_2, sa_2 = dqw, sqrt(dqx**2 + dqy**2 + dqz**2)
591 ca = ca_2**2 - sa_2**2
593 rotation_angle = atan2(sa, ca)
596 f = rotation_speed / sa_2
597 x, y, z = f*dqx, f*dqy, f*dqz
598 self.
imu_msg.angular_velocity.x = x
599 self.
imu_msg.angular_velocity.y = y
600 self.
imu_msg.angular_velocity.z = z
603 self.
vel_msg.twist.angular.x = x
604 self.
vel_msg.twist.angular.y = y
605 self.
vel_msg.twist.angular.z = z
610 x, y, z = o[
'gyrX'], o[
'gyrY'], o[
'gyrZ']
611 self.
imu_msg.angular_velocity.x = x
612 self.
imu_msg.angular_velocity.y = y
613 self.
imu_msg.angular_velocity.z = z
616 self.
vel_msg.twist.angular.x = x
617 self.
vel_msg.twist.angular.y = y
618 self.
vel_msg.twist.angular.z = z
623 def fill_from_GPS(o):
624 '''Fill messages with information from 'GPS' MTData2 block.''' 627 x, y, z = o[
'ecefX'], o[
'ecefY'], o[
'ecefZ']
632 vx, vy, vz = o[
'ecefVX'], o[
'ecefVY'], o[
'ecefVZ']
633 self.
vel_msg.twist.linear.x = vx * 0.01
634 self.
vel_msg.twist.linear.y = vy * 0.01
635 self.
vel_msg.twist.linear.z = vz * 0.01
637 itow, ns, week, f = o[
'iTOW'], o[
'fTOW'], o[
'Week'], o[
'Flags']
638 if (f & 0x0C) == 0xC:
639 secs, nsecs = stamp_from_itow(itow, ns=ns, week=week)
640 publish_time_ref(secs, nsecs,
'GPS Time')
646 itow, y, m, d, ns, f = o[
'iTOW'], o[
'year'], o[
'month'],\
647 o[
'day'], o[
'nano'], o[
'valid']
649 secs, nsecs = stamp_from_itow(itow, y, m, d, ns)
650 publish_time_ref(secs, nsecs,
'GPS Time UTC')
655 def fill_from_SCR(o):
656 '''Fill messages with information from 'SCR' MTData2 block.''' 660 def fill_from_Analog_In(o):
661 '''Fill messages with information from 'Analog In' MTData2 block. 664 self.anin1_msg.data = o[
'analogIn1']
665 self.pub_anin1 =
True 669 self.anin2_msg.data = o[
'analogIn2']
670 self.pub_anin2 =
True 674 def fill_from_Magnetic(o):
675 '''Fill messages with information from 'Magnetic' MTData2 block.''' 676 x, y, z = o[
'magX'], o[
'magY'], o[
'magZ']
677 self.mag_msg.magnetic_field.x = x
678 self.mag_msg.magnetic_field.y = y
679 self.mag_msg.magnetic_field.z = z
682 def fill_from_Velocity(o):
683 '''Fill messages with information from 'Velocity' MTData2 block.''' 684 x, y, z = convert_coords(o[
'velX'], o[
'velY'], o[
'velZ'],
686 self.vel_msg.twist.linear.x = x
687 self.vel_msg.twist.linear.y = y
688 self.vel_msg.twist.linear.z = z
691 def fill_from_Status(o):
692 '''Fill messages with information from 'Status' MTData2 block.''' 694 status = o[
'StatusByte']
695 fill_from_Stat(status)
699 status = o[
'StatusWord']
700 fill_from_Stat(status)
704 def find_handler_name(name):
705 return "fill_from_%s" % (name.replace(
" ",
"_"))
709 data = self.mt.read_measurement()
710 except mtdef.MTTimeoutException:
715 self.
h.stamp = rospy.Time.now()
722 for n, o
in data.items():
724 locals()[find_handler_name(n)](o)
726 rospy.logwarn(
"Unknown MTi data packet: '%s', ignoring." % n)
732 self.
imu_pub = rospy.Publisher(
'imu/data', Imu, queue_size=10)
737 self.
raw_gps_pub = rospy.Publisher(
'raw_fix', NavSatFix, queue_size=10)
742 self.
pos_gps_pub = rospy.Publisher(
'fix', NavSatFix, queue_size=10)
747 self.
vel_pub = rospy.Publisher(
'velocity', TwistStamped,
753 self.
mag_pub = rospy.Publisher(
'imu/mag', MagneticField,
759 self.
temp_pub = rospy.Publisher(
'temperature', Temperature,
765 self.
press_pub = rospy.Publisher(
'pressure', FluidPressure,
771 UInt16, queue_size=10)
781 self.
ecef_pub = rospy.Publisher(
'ecef', PointStamped,
787 self.
diag_pub = rospy.Publisher(
'/diagnostics',
788 DiagnosticArray, queue_size=10)
791 self.
str_pub.publish(str(data))
795 '''Create a ROS node and instantiate the class.''' 796 rospy.init_node(
'xsens_driver')
801 if __name__ ==
'__main__':
def get_param_list(name, default)
def matrix_from_diagonal(diagonal)
angular_velocity_covariance
def get_param(name, default)
linear_acceleration_covariance