driver.py
Go to the documentation of this file.
1 # Software License Agreement (BSD License)
2 #
3 # Copyright (c) 2013, Eric Perko
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 #
10 # * Redistributions of source code must retain the above copyright
11 # notice, this list of conditions and the following disclaimer.
12 # * Redistributions in binary form must reproduce the above
13 # copyright notice, this list of conditions and the following
14 # disclaimer in the documentation and/or other materials provided
15 # with the distribution.
16 # * Neither the names of the authors nor the names of their
17 # affiliated organizations may be used to endorse or promote products derived
18 # from this software without specific prior written permission.
19 #
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 # POSSIBILITY OF SUCH DAMAGE.
32 
33 """Provides a driver for NMEA GNSS devices."""
34 
35 import math
36 
37 import rospy
38 
39 from sensor_msgs.msg import NavSatFix, NavSatStatus, TimeReference
40 from geometry_msgs.msg import TwistStamped
41 
42 from libnmea_navsat_driver.checksum_utils import check_nmea_checksum
44 
45 
46 class RosNMEADriver(object):
47  """ROS driver for NMEA GNSS devices."""
48 
49  def __init__(self):
50  """Initialize the ROS NMEA driver.
51 
52  Creates the following ROS publishers:
53  NavSatFix publisher on the 'fix' channel.
54  TwistStamped publisher on the 'vel' channel.
55  QuaternionStamped publisher on the 'heading' channel.
56  TimeReference publisher on the 'time_reference' channel.
57 
58  Reads the following ROS parameters:
59  ~time_ref_source (str): The name of the source in published TimeReference messages. (default None)
60  ~useRMC (bool): If true, use RMC NMEA messages. If false, use GGA and VTG messages. (default False)
61  ~epe_quality0 (float): Value to use for default EPE quality for fix type 0. (default 1000000)
62  ~epe_quality1 (float): Value to use for default EPE quality for fix type 1. (default 4.0)
63  ~epe_quality2 (float): Value to use for default EPE quality for fix type 2. (default (0.1)
64  ~epe_quality4 (float): Value to use for default EPE quality for fix type 4. (default 0.02)
65  ~epe_quality5 (float): Value to use for default EPE quality for fix type 5. (default 4.0)
66  ~epe_quality9 (float): Value to use for default EPE quality for fix type 9. (default 3.0)
67  """
68  self.fix_pub = rospy.Publisher('fix', NavSatFix, queue_size=1)
69  self.vel_pub = rospy.Publisher('vel', TwistStamped, queue_size=1)
70  self.use_GNSS_time = rospy.get_param('~use_GNSS_time', False)
71  if not self.use_GNSS_time:
72  self.time_ref_pub = rospy.Publisher(
73  'time_reference', TimeReference, queue_size=1)
74 
75  self.time_ref_source = rospy.get_param('~time_ref_source', None)
76  self.use_RMC = rospy.get_param('~useRMC', False)
77  self.valid_fix = False
78 
79  def add_sentence(self, nmea_string, frame_id, timestamp=None):
80  """Public method to provide a new NMEA sentence to the driver.
81 
82  Args:
83  nmea_string (str): NMEA sentence in string form.
84  frame_id (str): TF frame ID of the GPS receiver.
85  timestamp(rospy.Time, optional): Time the sentence was received.
86  If timestamp is not specified, the current time is used.
87 
88  Returns:
89  bool: True if the NMEA string is successfully processed, False if there is an error.
90  """
91  if not check_nmea_checksum(nmea_string):
92  rospy.logwarn("Received a sentence with an invalid checksum. " +
93  "Sentence was: %s" % repr(nmea_string))
94  return False
95 
97  nmea_string)
98  if not parsed_sentence:
99  rospy.logdebug(
100  "Failed to parse NMEA sentence. Sentence was: %s" %
101  nmea_string)
102  return False
103 
104  if timestamp:
105  current_time = timestamp
106  else:
107  current_time = rospy.get_rostime()
108  current_fix = NavSatFix()
109  current_fix.header.stamp = current_time
110  current_fix.header.frame_id = frame_id
111  if not self.use_GNSS_time:
112  current_time_ref = TimeReference()
113  current_time_ref.header.stamp = current_time
114  current_time_ref.header.frame_id = frame_id
115  if self.time_ref_source:
116  current_time_ref.source = self.time_ref_source
117  else:
118  current_time_ref.source = frame_id
119 
120  if not self.use_RMC and 'GGA' in parsed_sentence:
121  data = parsed_sentence['GGA']
122 
123  if self.use_GNSS_time:
124  if math.isnan(data['utc_time'][0]):
125  rospy.logwarn("Time in the NMEA sentence is NOT valid")
126  return False
127  current_fix.header.stamp = rospy.Time(data['utc_time'][0], data['utc_time'][1])
128 
129  gps_qual = data['fix_type']
130  if gps_qual == 0:
131  current_fix.status.status = NavSatStatus.STATUS_NO_FIX
132  elif gps_qual == 1:
133  current_fix.status.status = NavSatStatus.STATUS_FIX
134  elif gps_qual == 2:
135  current_fix.status.status = NavSatStatus.STATUS_SBAS_FIX
136  elif gps_qual in (4, 5):
137  current_fix.status.status = NavSatStatus.STATUS_GBAS_FIX
138  elif gps_qual == 9:
139  # Support specifically for NOVATEL OEM4 recievers which report WAAS fix as 9
140  # http://www.novatel.com/support/known-solutions/which-novatel-position-types-correspond-to-the-gga-quality-indicator/
141  current_fix.status.status = NavSatStatus.STATUS_SBAS_FIX
142  else:
143  current_fix.status.status = NavSatStatus.STATUS_NO_FIX
144 
145  if gps_qual > 0:
146  self.valid_fix = True
147  else:
148  self.valid_fix = False
149 
150  current_fix.status.service = NavSatStatus.SERVICE_GPS
151 
152  current_fix.header.stamp = current_time
153 
154  latitude = data['latitude']
155  if data['latitude_direction'] == 'S':
156  latitude = -latitude
157  current_fix.latitude = latitude
158 
159  longitude = data['longitude']
160  if data['longitude_direction'] == 'W':
161  longitude = -longitude
162  current_fix.longitude = longitude
163 
164  hdop = data['hdop']
165  current_fix.position_covariance[0] = hdop ** 2
166  current_fix.position_covariance[4] = hdop ** 2
167  current_fix.position_covariance[8] = (2 * hdop) ** 2 # FIXME
168  current_fix.position_covariance_type = \
169  NavSatFix.COVARIANCE_TYPE_APPROXIMATED
170 
171  # Altitude is above ellipsoid, so adjust for mean-sea-level
172  altitude = data['altitude'] + data['mean_sea_level']
173  current_fix.altitude = altitude
174 
175  self.fix_pub.publish(current_fix)
176 
177  if not (math.isnan(data['utc_time'][0]) or self.use_GNSS_time):
178  current_time_ref.time_ref = rospy.Time(
179  data['utc_time'][0], data['utc_time'][1])
180  self.last_valid_fix_time = current_time_ref
181  self.time_ref_pub.publish(current_time_ref)
182 
183  elif not self.use_RMC and 'VTG' in parsed_sentence:
184  data = parsed_sentence['VTG']
185 
186  # Only report VTG data when you've received a valid GGA fix as
187  # well.
188  if self.valid_fix:
189  current_vel = TwistStamped()
190  current_vel.header.stamp = current_time
191  current_vel.header.frame_id = frame_id
192  current_vel.twist.linear.x = data['speed'] * math.sin(data['true_course'])
193  current_vel.twist.linear.y = data['speed'] * math.cos(data['true_course'])
194  self.vel_pub.publish(current_vel)
195 
196  elif 'RMC' in parsed_sentence:
197  data = parsed_sentence['RMC']
198 
199  if self.use_GNSS_time:
200  if math.isnan(data['utc_time'][0]):
201  rospy.logwarn("Time in the NMEA sentence is NOT valid")
202  return False
203  current_fix.header.stamp = rospy.Time(data['utc_time'][0], data['utc_time'][1])
204 
205  # Only publish a fix from RMC if the use_RMC flag is set.
206  if self.use_RMC:
207  if data['fix_valid']:
208  current_fix.status.status = NavSatStatus.STATUS_FIX
209  else:
210  current_fix.status.status = NavSatStatus.STATUS_NO_FIX
211 
212  current_fix.status.service = NavSatStatus.SERVICE_GPS
213 
214  latitude = data['latitude']
215  if data['latitude_direction'] == 'S':
216  latitude = -latitude
217  current_fix.latitude = latitude
218 
219  longitude = data['longitude']
220  if data['longitude_direction'] == 'W':
221  longitude = -longitude
222  current_fix.longitude = longitude
223 
224  current_fix.altitude = float('NaN')
225  current_fix.position_covariance_type = \
226  NavSatFix.COVARIANCE_TYPE_UNKNOWN
227 
228  self.fix_pub.publish(current_fix)
229 
230  if not (math.isnan(data['utc_time'][0]) or self.use_GNSS_time):
231  current_time_ref.time_ref = rospy.Time(
232  data['utc_time'][0], data['utc_time'][1])
233  self.time_ref_pub.publish(current_time_ref)
234 
235  # Publish velocity from RMC regardless, since GGA doesn't provide
236  # it.
237  if data['fix_valid']:
238  current_vel = TwistStamped()
239  current_vel.header.stamp = current_time
240  current_vel.header.frame_id = frame_id
241  current_vel.twist.linear.x = data['speed'] * \
242  math.sin(data['true_course'])
243  current_vel.twist.linear.y = data['speed'] * \
244  math.cos(data['true_course'])
245  self.vel_pub.publish(current_vel)
246  else:
247  return False
248 
249  @staticmethod
251  """Get the TF frame_id.
252 
253  Queries rosparam for the ~frame_id param. If a tf_prefix param is set,
254  the frame_id is prefixed with the prefix.
255 
256  Returns:
257  str: The fully-qualified TF frame ID.
258  """
259  frame_id = rospy.get_param('~frame_id', 'gps')
260  if frame_id[0] != "/":
261  # Add the TF prefix
262  prefix = ""
263  prefix_param = rospy.search_param('tf_prefix')
264  if prefix_param:
265  prefix = rospy.get_param(prefix_param)
266  if prefix[0] != "/":
267  prefix = "/%s" % prefix
268  return "%s/%s" % (prefix, frame_id)
269  else:
270  return frame_id
def add_sentence(self, nmea_string, frame_id, timestamp=None)
Definition: driver.py:79
def parse_nmea_sentence(nmea_sentence)
Definition: parser.py:269


nmea_navsat_driver
Author(s): Eric Perko , Steven Martin
autogenerated on Mon Feb 28 2022 22:57:09