5050# Internal helper parsing functions.
5151# These handle input that might be none or null and return none instead of
5252# throwing errors.
53+
54+
5355def _parse_degrees (nmea_data ):
5456 # Parse a NMEA lat/long data pair 'dddmm.mmmm' into a pure degrees value.
5557 # Where ddd is the degrees, mm.mmmm is the minutes.
@@ -60,26 +62,31 @@ def _parse_degrees(nmea_data):
6062 minutes = raw % 100
6163 return deg + minutes / 60
6264
65+
6366def _parse_int (nmea_data ):
6467 if nmea_data is None or nmea_data == '' :
6568 return None
6669 return int (nmea_data )
6770
71+
6872def _parse_float (nmea_data ):
6973 if nmea_data is None or nmea_data == '' :
7074 return None
7175 return float (nmea_data )
7276
77+
7378def _parse_str (nmea_data ):
7479 if nmea_data is None or nmea_data == '' :
7580 return None
7681 return str (nmea_data )
7782
7883# lint warning about too many attributes disabled
7984#pylint: disable-msg=R0902
85+
86+
8087class GPS :
81- """GPS parsing module. Can parse simple NMEA data sentences from serial GPS
82- modules to read latitude, longitude, and more.
88+ """GPS parsing module. Can parse simple NMEA data sentences from serial
89+ GPS modules to read latitude, longitude, and more.
8390 """
8491 def __init__ (self , uart , debug = False ):
8592 self ._uart = uart
@@ -90,13 +97,21 @@ def __init__(self, uart, debug=False):
9097 self .fix_quality = None
9198 self .fix_quality_3d = None
9299 self .satellites = None
100+ self .satellites_prev = None
93101 self .horizontal_dilution = None
94102 self .altitude_m = None
95103 self .height_geoid = None
96104 self .speed_knots = None
97105 self .track_angle_deg = None
98106 self .sats = None
99107 self .isactivedata = None
108+ self .true_track = None
109+ self .mag_track = None
110+ self .sat_prns = None
111+ self .sel_mode = None
112+ self .pdop = None
113+ self .hdop = None
114+ self .vdop = None
100115 self .debug = debug
101116
102117 def update (self ):
@@ -109,15 +124,14 @@ def update(self):
109124 try :
110125 sentence = self ._parse_sentence ()
111126 except UnicodeError :
112- print ("UnicodeError" )
113127 return None
114128 if sentence is None :
115129 return False
116130 if self .debug :
117131 print (sentence )
118132 data_type , args = sentence
119133 data_type = bytes (data_type .upper (), "ascii" )
120- #return sentence
134+ # return sentence
121135 if data_type == b'GPGLL' : # GLL, Geographic Position – Latitude/Longitude
122136 self ._parse_gpgll (args )
123137 elif data_type == b'GPRMC' : # RMC, minimum location info
@@ -149,7 +163,9 @@ def has_fix(self):
149163
150164 @property
151165 def has_3d_fix (self ):
152- """True if a current 3d fix for location information is available"""
166+ """Returns true if there is a 3d fix available.
167+ use has_fix to determine if a 2d fix is available,
168+ passing it the same data"""
153169 return self .fix_quality_3d is not None and self .fix_quality_3d >= 2
154170
155171 @property
@@ -162,8 +178,8 @@ def _parse_sentence(self):
162178 # pylint: disable=len-as-condition
163179 # This needs to be refactored when it can be tested.
164180
165- # Only continue if we have at least 64 bytes in the input buffer
166- if self ._uart .in_waiting < 64 :
181+ # Only continue if we have at least 32 bytes in the input buffer
182+ if self ._uart .in_waiting < 32 :
167183 return None
168184
169185 sentence = self ._uart .readline ()
@@ -195,7 +211,7 @@ def _parse_sentence(self):
195211 def _parse_gpgll (self , args ):
196212 data = args .split (',' )
197213 if data is None or data [0 ] is None :
198- return # Unexpected number of params.
214+ return # Unexpected number of params.
199215
200216 # Parse latitude and longitude.
201217 self .latitude = _parse_degrees (data [0 ])
@@ -262,7 +278,7 @@ def _parse_gprmc(self, args):
262278 if data [8 ] is not None and len (data [8 ]) == 6 :
263279 day = int (data [8 ][0 :2 ])
264280 month = int (data [8 ][2 :4 ])
265- year = 2000 + int (data [8 ][4 :6 ]) # Y2k bug, 2 digit date assumption.
281+ year = 2000 + int (data [8 ][4 :6 ]) # Y2k bug, 2 digit year assumption.
266282 # This is a problem with the NMEA
267283 # spec and not this code.
268284 if self .timestamp_utc is not None :
@@ -315,3 +331,72 @@ def _parse_gpgga(self, args):
315331 self .horizontal_dilution = _parse_float (data [7 ])
316332 self .altitude_m = _parse_float (data [8 ])
317333 self .height_geoid = _parse_float (data [10 ])
334+
335+ def _parse_gpgsa (self , args ):
336+ data = args .split (',' )
337+ if data is None :
338+ return # Unexpected number of params
339+
340+ # Parse selection mode
341+ self .sel_mode = _parse_str (data [0 ])
342+ # Parse 3d fix
343+ self .fix_quality_3d = _parse_int (data [1 ])
344+ satlist = list (filter (None , data [2 :- 4 ]))
345+ self .sat_prns = {}
346+ for i , sat in enumerate (satlist , 1 ):
347+ self .sat_prns ["gps{}" .format (i )] = _parse_int (sat )
348+
349+ # Parse PDOP, dilution of precision
350+ self .pdop = _parse_float (data [- 3 ])
351+ # Parse HDOP, horizontal dilution of precision
352+ self .hdop = _parse_float (data [- 2 ])
353+ # Parse VDOP, vertical dilution of precision
354+ self .vdop = _parse_float (data [- 1 ])
355+
356+ def _parse_gpgsv (self , args ):
357+ # Parse the arguments (everything after data type) for NMEA GPGGA
358+ # 3D location fix sentence.
359+ data = args .split (',' )
360+ if data is None :
361+ return # Unexpected number of params.
362+
363+ # Parse number of messages
364+ self .total_mess_num = _parse_int (data [0 ]) # Total number of messages
365+ # Parse message number
366+ self .mess_num = _parse_int (data [1 ]) # Message number
367+ # Parse number of satellites in view
368+ self .satellites = _parse_int (data [2 ]) # Number of satellites
369+
370+ if len (data ) < 3 :
371+ return
372+
373+ sat_tup = data [3 :]
374+
375+ satdict = {}
376+ for i in range (len (sat_tup )/ 4 ):
377+ j = i * 4
378+ key = "gps{}" .format (i + (4 * (self .mess_num - 1 )))
379+ satnum = _parse_int (sat_tup [0 + j ]) # Satellite number
380+ satdeg = _parse_int (sat_tup [1 + j ]) # Elevation in degrees
381+ satazim = _parse_int (sat_tup [2 + j ]) # Azimuth in degrees
382+ satsnr = _parse_int (sat_tup [3 + j ]) # signal-to-noise ratio in dB
383+ value = (satnum , satdeg , satazim , satsnr )
384+ satdict [key ] = value
385+
386+ if self .sats is None :
387+ self .sats = {}
388+ for satnum in satdict :
389+ self .sats [satnum ] = satdict [satnum ]
390+
391+ try :
392+ if self .satellites < self .satellites_prev :
393+ for i in self .sats :
394+ try :
395+ if int (i [- 2 ]) >= self .satellites :
396+ del self .sats [i ]
397+ except ValueError :
398+ if int (i [- 1 ]) >= self .satellites :
399+ del self .sats [i ]
400+ except TypeError :
401+ pass
402+ self .satellites_prev = self .satellites
0 commit comments