publisher.py
Go to the documentation of this file.
00001 #! /usr/bin/env python
00002 # -*- coding: utf-8 -*-
00003 #     _____
00004 #    /  _  \
00005 #   / _/ \  \
00006 #  / / \_/   \
00007 # /  \_/  _   \  ___  _    ___   ___   ____   ____   ___   _____  _   _
00008 # \  / \_/ \  / /  _\| |  | __| / _ \ | ┌┐ \ | ┌┐ \ / _ \ |_   _|| | | |
00009 #  \ \_/ \_/ /  | |  | |  | └─┐| |_| || └┘ / | └┘_/| |_| |  | |  | └─┘ |
00010 #   \  \_/  /   | |_ | |_ | ┌─┘|  _  || |\ \ | |   |  _  |  | |  | ┌─┐ |
00011 #    \_____/    \___/|___||___||_| |_||_| \_\|_|   |_| |_|  |_|  |_| |_|
00012 #            ROBOTICS™
00013 #
00014 #
00015 #  Copyright © 2012 Clearpath Robotics, Inc. 
00016 #  All Rights Reserved
00017 #  
00018 # Redistribution and use in source and binary forms, with or without
00019 # modification, are permitted provided that the following conditions are met:
00020 #     * Redistributions of source code must retain the above copyright
00021 #       notice, this list of conditions and the following disclaimer.
00022 #     * Redistributions in binary form must reproduce the above copyright
00023 #       notice, this list of conditions and the following disclaimer in the
00024 #       documentation and/or other materials provided with the distribution.
00025 #     * Neither the name of Clearpath Robotics, Inc. nor the
00026 #       names of its contributors may be used to endorse or promote products
00027 #       derived from this software without specific prior written permission.
00028 # 
00029 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
00030 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00031 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00032 # DISCLAIMED. IN NO EVENT SHALL CLEARPATH ROBOTICS, INC. BE LIABLE FOR ANY
00033 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
00034 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00035 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
00036 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00037 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00038 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00039 #
00040 # Please send comments, questions, or patches to skynet@clearpathrobotics.com
00041 #
00042 
00043 # ROS
00044 import rospy
00045 import tf
00046 import PyKDL
00047 
00048 # Applanix node internal messages & modules
00049 from applanix_msgs.msg import NavigationSolution, GNSSStatus, IMUData
00050 import geodesy.utm
00051 
00052 # ROS standard messages
00053 from sensor_msgs.msg import Imu, NavSatFix, NavSatStatus
00054 from nav_msgs.msg import Odometry
00055 from geometry_msgs.msg import Quaternion, Point, Pose
00056 
00057 # Other
00058 from math import radians as RAD
00059 
00060 # FIXED COVARIANCES
00061 # TODO: Work these out...
00062 IMU_ORIENT_COVAR = [1e-3, 0, 0,
00063                     0, 1e-3, 0,
00064                     0, 0, 1e-3]
00065 
00066 IMU_VEL_COVAR = [1e-3, 0, 0,
00067                  0, 1e-3, 0,
00068                  0, 0, 1e-3]
00069 
00070 IMU_ACCEL_COVAR = [1e-3, 0, 0,
00071                    0, 1e-3, 0,
00072                    0, 0, 1e-3]
00073 
00074 NAVSAT_COVAR = [1, 0, 0,
00075                 0, 1, 0,
00076                 0, 0, 1]
00077 
00078 POSE_COVAR = [1, 0, 0, 0, 0, 0,
00079               0, 1, 0, 0, 0, 0,
00080               0, 0, 1, 0, 0, 0,
00081               0, 0, 0, 0.1, 0, 0,
00082               0, 0, 0, 0, 0.1, 0,
00083               0, 0, 0, 0, 0, 0.1]
00084 
00085 TWIST_COVAR = [1, 0, 0, 0, 0, 0,
00086                0, 1, 0, 0, 0, 0,
00087                0, 0, 1, 0, 0, 0,
00088                0, 0, 0, 0.1, 0, 0,
00089                0, 0, 0, 0, 0.1, 0,
00090                0, 0, 0, 0, 0, 0.1]
00091 
00092 class ApplanixPublisher(object):
00093 
00094     def __init__(self):
00095         rospy.init_node('applanix_publisher')
00096         # Parameters
00097         self.publish_tf = rospy.get_param('~publish_tf', False)
00098         self.odom_frame = rospy.get_param('~odom_frame', 'odom_combined')
00099         self.base_frame = rospy.get_param('~base_frame', 'base_footprint')
00100         self.zero_start = rospy.get_param('~zero_start', False)
00101         
00102         origin_param = rospy.get_param('/gps_origin', None)
00103         self.origin = Point()
00104         if origin_param is not None and origin_param != "None":
00105             self.zero_start = False
00106             self.origin.x = origin_param["east"]
00107             self.origin.y = origin_param["north"]
00108             self.origin.z = origin_param["alt"]
00109         elif not self.zero_start:
00110             origin_param = {
00111                 "east"  : self.origin.x,
00112                 "north" : self.origin.y,
00113                 "alt"   : self.origin.z,
00114             }
00115             rospy.set_param('/gps_origin', origin_param)
00116 
00117         # Topic publishers
00118         self.pub_imu = rospy.Publisher('imu_data', Imu, queue_size=5)
00119         self.pub_odom = rospy.Publisher('gps_odom', Odometry, queue_size=5)
00120         self.pub_origin = rospy.Publisher('origin', Pose, queue_size=5)
00121         self.pub_navsatfix = rospy.Publisher('gps_fix', NavSatFix, queue_size=5)
00122         self.pub_navsatstatus = rospy.Publisher('gps_status', NavSatStatus, queue_size=5)
00123         if self.publish_tf:
00124             self.tf_broadcast = tf.TransformBroadcaster()
00125 
00126         # Init nav status
00127         self.nav_status = NavSatStatus()    # We need this for the NavSatFix broadcaster
00128         self.nav_status.status = NavSatStatus.STATUS_NO_FIX
00129         self.nav_status.service = NavSatStatus.SERVICE_GPS
00130 
00131         self.init = False       # If we've been initialized
00132         
00133         # Subscribed topics
00134         rospy.Subscriber('nav', NavigationSolution, self.navigation_handler)
00135         rospy.Subscriber('status/gnss/primary', GNSSStatus, self.status_handler)
00136        
00137     def navigation_handler(self, data):
00138         """ Rebroadcasts navigation data in the following formats:
00139         1) /odom => /base footprint transform (if enabled, as per REP 105)
00140         2) Odometry message, with parent/child IDs as in #1
00141         3) NavSatFix message, for systems which are knowledgeable about GPS stuff
00142         4) IMU messages
00143         """
00144         rospy.logdebug("Navigation received")
00145         # If we don't have a fix, don't publish anything...
00146         if self.nav_status.status == NavSatStatus.STATUS_NO_FIX:
00147             return
00148         
00149         # Changing from NED from the Applanix to ENU in ROS
00150         # Roll is still roll, since it's WRT to the x axis of the vehicle
00151         # Pitch is -ve since axis goes the other way (+y to right vs left)
00152         # Yaw (or heading) in Applanix is clockwise starting with North
00153         # In ROS it's counterclockwise startin with East 
00154         orient = PyKDL.Rotation.RPY(RAD(data.roll), RAD(-data.pitch), RAD(90-data.heading)).GetQuaternion()
00155 
00156         # UTM conversion
00157         utm_pos = geodesy.utm.fromLatLong(data.latitude, data.longitude)
00158         # Initialize starting point if we haven't yet
00159         if not self.init and self.zero_start:
00160             self.origin.x = utm_pos.easting
00161             self.origin.y = utm_pos.northing
00162             self.origin.z = data.altitude
00163             self.init = True
00164             origin_param = {
00165                 "east"  : self.origin.x,
00166                 "north" : self.origin.y,
00167                 "alt"   : self.origin.z,
00168             }
00169             rospy.set_param('/gps_origin', origin_param)
00170 
00171         # Publish origin reference for others to know about
00172         p = Pose()
00173         p.position.x = self.origin.x
00174         p.position.y = self.origin.y
00175         p.position.z = self.origin.z
00176         self.pub_origin.publish(p)
00177 
00178         #
00179         # Odometry 
00180         # TODO: Work out these covariances properly from DOP
00181         #
00182         odom = Odometry()
00183         odom.header.stamp = rospy.Time.now()
00184         odom.header.frame_id = self.odom_frame
00185         odom.child_frame_id = self.base_frame
00186         odom.pose.pose.position.x = utm_pos.easting - self.origin.x
00187         odom.pose.pose.position.y = utm_pos.northing - self.origin.y
00188         odom.pose.pose.position.z = data.altitude - self.origin.z
00189         odom.pose.pose.orientation = Quaternion(*orient)
00190         odom.pose.covariance = POSE_COVAR
00191 
00192         # Twist is relative to /reference frame or /vehicle frame and
00193         # NED to ENU conversion is needed here too
00194         odom.twist.twist.linear.x = data.east_vel
00195         odom.twist.twist.linear.y = data.north_vel
00196         odom.twist.twist.linear.z = -data.down_vel
00197         odom.twist.twist.angular.x = RAD(data.ang_rate_long)
00198         odom.twist.twist.angular.y = RAD(-data.ang_rate_trans)
00199         odom.twist.twist.angular.z = RAD(-data.ang_rate_down)
00200         odom.twist.covariance = TWIST_COVAR
00201 
00202         self.pub_odom.publish(odom)
00203 
00204         t_tf =  odom.pose.pose.position.x, odom.pose.pose.position.y, odom.pose.pose.position.z
00205         q_tf =  Quaternion(*orient).x, Quaternion(*orient).y, Quaternion(*orient).z, Quaternion(*orient).w
00206         #
00207         # Odometry transform (if required)
00208         #
00209         if self.publish_tf:
00210             self.tf_broadcast.sendTransform(t_tf,q_tf,
00211                  odom.header.stamp,odom.child_frame_id, odom.header.frame_id)
00212 
00213         # 
00214         # NavSatFix
00215         # TODO: Work out these covariances properly from DOP
00216         #
00217         navsat = NavSatFix()
00218         navsat.header.stamp = rospy.Time.now()
00219         navsat.header.frame_id = self.odom_frame
00220         navsat.status = self.nav_status
00221 
00222         navsat.latitude = data.latitude
00223         navsat.longitude = data.longitude
00224         navsat.altitude = data.altitude
00225 
00226         navsat.position_covariance = NAVSAT_COVAR
00227         navsat.position_covariance_type = NavSatFix.COVARIANCE_TYPE_UNKNOWN
00228         
00229         self.pub_navsatfix.publish(navsat)
00230         
00231         #
00232         # IMU
00233         # TODO: Work out these covariances properly
00234         #
00235         imu = Imu()
00236         imu.header.stamp == rospy.Time.now()
00237         imu.header.frame_id = self.base_frame
00238       
00239         # Orientation
00240         imu.orientation = Quaternion(*orient)
00241         imu.orientation_covariance = IMU_ORIENT_COVAR
00242  
00243         # Angular rates
00244         imu.angular_velocity.x = RAD(data.ang_rate_long)
00245         imu.angular_velocity.y = RAD(-data.ang_rate_trans)
00246         imu.angular_velocity.z = RAD(-data.ang_rate_down)
00247         imu.angular_velocity_covariance = IMU_VEL_COVAR
00248 
00249         # Linear acceleration
00250         imu.linear_acceleration.x = data.long_accel
00251         imu.linear_acceleration.y = data.trans_accel
00252         imu.linear_acceleration.z = data.down_accel
00253         imu.linear_acceleration_covariance = IMU_ACCEL_COVAR
00254 
00255         self.pub_imu.publish(imu)
00256         
00257          
00258         pass
00259 
00260     def status_handler(self, data):
00261         """ Rebroadcasts GNSS status as a standard NavSatStatus message """
00262         # In the below, not sure about mapping the "DGPS" status to SBAS instead of GBAS
00263         solution_map = {
00264             GNSSStatus.SOLUTION_UNKNOWN: NavSatStatus.STATUS_NO_FIX,
00265             GNSSStatus.SOLUTION_NO_DATA: NavSatStatus.STATUS_NO_FIX,
00266             GNSSStatus.SOLUTION_HORIZONTAL_CA: NavSatStatus.STATUS_FIX,
00267             GNSSStatus.SOLUTION_3D_CA: NavSatStatus.STATUS_FIX,
00268             GNSSStatus.SOLUTION_HORIZONTAL_DGPS: NavSatStatus.STATUS_SBAS_FIX,
00269             GNSSStatus.SOLUTION_3D_DGPS: NavSatStatus.STATUS_SBAS_FIX,
00270             GNSSStatus.SOLUTION_FLOAT_RTK: NavSatStatus.STATUS_GBAS_FIX,
00271             GNSSStatus.SOLUTION_WIDE_LANE_RTK: NavSatStatus.STATUS_GBAS_FIX,
00272             GNSSStatus.SOLUTION_NARROW_LANE_RTK: NavSatStatus.STATUS_GBAS_FIX,
00273             GNSSStatus.SOLUTION_P_CODE: NavSatStatus.STATUS_FIX,
00274             GNSSStatus.SOLUTION_OMNISTAR_HP: NavSatStatus.STATUS_SBAS_FIX,
00275             GNSSStatus.SOLUTION_OMNISTAR_XP: NavSatStatus.STATUS_SBAS_FIX,
00276             GNSSStatus.SOLUTION_OMNISTAR_VBS: NavSatStatus.STATUS_SBAS_FIX,
00277             }
00278         self.nav_status.status = solution_map.get(data.solution_status,NavSatStatus.STATUS_NO_FIX)
00279 
00280         # Assume GPS - this isn't exposed
00281         self.nav_status.service = NavSatStatus.SERVICE_GPS
00282             
00283         self.pub_navsatstatus.publish(self.nav_status)
00284 
00285 
00286 def main():
00287     node = ApplanixPublisher()
00288     rospy.spin()


applanix_bridge
Author(s): Mike Purvis
autogenerated on Thu Aug 27 2015 12:15:53