parser.py
Go to the documentation of this file.
00001 # Software License Agreement (BSD License)
00002 #
00003 # Copyright (c) 2013, Eric Perko
00004 # All rights reserved.
00005 #
00006 # Redistribution and use in source and binary forms, with or without
00007 # modification, are permitted provided that the following conditions
00008 # are met:
00009 #
00010 #  * Redistributions of source code must retain the above copyright
00011 #    notice, this list of conditions and the following disclaimer.
00012 #  * Redistributions in binary form must reproduce the above
00013 #    copyright notice, this list of conditions and the following
00014 #    disclaimer in the documentation and/or other materials provided
00015 #    with the distribution.
00016 #  * Neither the names of the authors nor the names of their
00017 #    affiliated organizations may be used to endorse or promote products derived
00018 #    from this software without specific prior written permission.
00019 #
00020 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00021 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00022 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00023 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00024 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00025 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00026 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00027 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00028 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00029 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00030 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00031 # POSSIBILITY OF SUCH DAMAGE.
00032 
00033 import re
00034 import time
00035 import calendar
00036 import math
00037 import logging
00038 logger = logging.getLogger('rosout')
00039 
00040 
00041 def safe_float(field):
00042     try:
00043         return float(field)
00044     except ValueError:
00045         return float('NaN')
00046 
00047 
00048 def safe_int(field):
00049     try:
00050         return int(field)
00051     except ValueError:
00052         return 0
00053 
00054 
00055 def convert_latitude(field):
00056     return safe_float(field[0:2]) + safe_float(field[2:]) / 60.0
00057 
00058 
00059 def convert_longitude(field):
00060     return safe_float(field[0:3]) + safe_float(field[3:]) / 60.0
00061 
00062 
00063 def convert_time(nmea_utc):
00064     # Get current time in UTC for date information
00065     utc_struct = time.gmtime()  # immutable, so cannot modify this one
00066     utc_list = list(utc_struct)
00067     # If one of the time fields is empty, return NaN seconds
00068     if not nmea_utc[0:2] or not nmea_utc[2:4] or not nmea_utc[4:6]:
00069         return float('NaN')
00070     else:
00071         hours = int(nmea_utc[0:2])
00072         minutes = int(nmea_utc[2:4])
00073         seconds = int(nmea_utc[4:6])
00074         utc_list[3] = hours
00075         utc_list[4] = minutes
00076         utc_list[5] = seconds
00077         unix_time = calendar.timegm(tuple(utc_list))
00078         return unix_time
00079 
00080 
00081 def convert_status_flag(status_flag):
00082     if status_flag == "A":
00083         return True
00084     elif status_flag == "V":
00085         return False
00086     else:
00087         return False
00088 
00089 
00090 def convert_knots_to_mps(knots):
00091     return safe_float(knots) * 0.514444444444
00092 
00093 
00094 # Need this wrapper because math.radians doesn't auto convert inputs
00095 def convert_deg_to_rads(degs):
00096     return math.radians(safe_float(degs))
00097 
00098 """Format for this is a sentence identifier (e.g. "GGA") as the key, with a
00099 tuple of tuples where each tuple is a field name, conversion function and index
00100 into the split sentence"""
00101 parse_maps = {
00102     "GGA": [
00103         ("fix_type", int, 6),
00104         ("latitude", convert_latitude, 2),
00105         ("latitude_direction", str, 3),
00106         ("longitude", convert_longitude, 4),
00107         ("longitude_direction", str, 5),
00108         ("altitude", safe_float, 9),
00109         ("mean_sea_level", safe_float, 11),
00110         ("hdop", safe_float, 8),
00111         ("num_satellites", safe_int, 7),
00112         ("utc_time", convert_time, 1),
00113         ],
00114     "RMC": [
00115         ("utc_time", convert_time, 1),
00116         ("fix_valid", convert_status_flag, 2),
00117         ("latitude", convert_latitude, 3),
00118         ("latitude_direction", str, 4),
00119         ("longitude", convert_longitude, 5),
00120         ("longitude_direction", str, 6),
00121         ("speed", convert_knots_to_mps, 7),
00122         ("true_course", convert_deg_to_rads, 8),
00123         ]
00124     }
00125 
00126 
00127 def parse_nmea_sentence(nmea_sentence):
00128     # Check for a valid nmea sentence
00129     if not re.match('(^\$GP|^\$GN|^\$GL).*\*[0-9A-Fa-f]{2}$', nmea_sentence):
00130         logger.debug("Regex didn't match, sentence not valid NMEA? Sentence was: %s"
00131                      % repr(nmea_sentence))
00132         return False
00133     fields = [field.strip(',') for field in nmea_sentence.split(',')]
00134 
00135     # Ignore the $ and talker ID portions (e.g. GP)
00136     sentence_type = fields[0][3:]
00137 
00138     if not sentence_type in parse_maps:
00139         logger.debug("Sentence type %s not in parse map, ignoring."
00140                      % repr(sentence_type))
00141         return False
00142 
00143     parse_map = parse_maps[sentence_type]
00144 
00145     parsed_sentence = {}
00146     for entry in parse_map:
00147         parsed_sentence[entry[0]] = entry[1](fields[entry[2]])
00148 
00149     return {sentence_type: parsed_sentence}


nmea_navsat_driver
Author(s): Eric Perko , Steven Martin
autogenerated on Sat Dec 17 2016 03:51:23