@@ -70,6 +70,11 @@ def _parse_float(nmea_data):
7070 return None
7171 return float (nmea_data )
7272
73+ def _parse_str (nmea_data ):
74+ if nmea_data is None or nmea_data == '' :
75+ return None
76+ return str (nmea_data )
77+
7378# lint warning about too many attributes disabled
7479#pylint: disable-msg=R0902
7580class GPS :
@@ -83,18 +88,20 @@ def __init__(self, uart, debug=False):
8388 self .latitude = None
8489 self .longitude = None
8590 self .fix_quality = None
91+ self .fix_quality_3d = None
8692 self .satellites = None
8793 self .horizontal_dilution = None
8894 self .altitude_m = None
8995 self .height_geoid = None
90- self .velocity_knots = None
9196 self .speed_knots = None
97+ self .speed_kmh = None
9298 self .track_angle_deg = None
9399 self .total_mess_num = None
94100 self .mess_num = None
95- self .gps0 = None
96- self .gps1 = None
97- self .gps2 = None
101+ self .sats = None
102+ self .isactivedata = None
103+ self .true_track = None
104+ self .mag_track = None
98105 self .debug = debug
99106
100107 def update (self ):
@@ -115,11 +122,18 @@ def update(self):
115122 data_type , args = sentence
116123 data_type = bytes (data_type .upper (), "ascii" )
117124 #return sentence
118- if data_type == b'GPGGA' : # GGA, 3d location fix
119- self ._parse_gpgga (args )
120- elif data_type == b'GPRMC' : # RMC, minimum location info
125+
126+ if data_type == b'GPGLL' : # GLL, Geographic Position – Latitude/Longitude
127+ self ._parse_gpgll (args )
128+ elif data_type == b'GPRMC' : # RMC, minimum location info
121129 self ._parse_gprmc (args )
122- elif data_type == b'GPGSV' :
130+ elif data_type == b'GPVTG' : # VTG, Track Made Good and Ground Speed
131+ self ._parse_gpvtg (args )
132+ elif data_type == b'GPGGA' : # GGA, 3d location fix
133+ self ._parse_gpgga (args )
134+ elif data_type == b'GPGSA' : # GSA, GPS DOP and active satellites
135+ self ._parse_gpgsa (args )
136+ elif data_type == b'GPGSV' : # GSV, Satellites in view
123137 self ._parse_gpgsv (args )
124138 return True
125139
@@ -143,6 +157,10 @@ def send_command(self, command, add_checksum=True):
143157 def has_fix (self ):
144158 """True if a current fix for location information is available."""
145159 return self .fix_quality is not None and self .fix_quality >= 1
160+
161+ @property
162+ def has_3d_fix (self ):
163+ return self .fix_quality_3d is not None and self .fix_quality_3d >= 2
146164
147165 @property
148166 def datetime (self ):
@@ -179,15 +197,22 @@ def _parse_sentence(self):
179197 data_type = sentence [1 :delineator ]
180198 return (data_type , sentence [delineator + 1 :])
181199
182-
183- def _parse_gpgga (self , args ):
184- # Parse the arguments (everything after data type) for NMEA GPGGA
185- # 3D location fix sentence.
200+ def _parse_gpgll (self , args ):
186201 data = args .split (',' )
187- if data is None or len (data ) != 14 :
188- return # Unexpected number of params.
189- # Parse fix time.
190- time_utc = int (_parse_float (data [0 ]))
202+ if data is None or len (data ) < 11 or data [0 ] is None :
203+ return # Unexpected number of params.
204+
205+ # Parse latitude and longitude.
206+ self .latitude = _parse_degrees (data [0 ])
207+ if self .latitude is not None and \
208+ data [1 ] is not None and data [1 ].lower () == 's' :
209+ self .latitude *= - 1.0
210+ self .longitude = _parse_degrees (data [2 ])
211+ if self .longitude is not None and \
212+ data [3 ] is not None and data [3 ].lower () == 'w' :
213+ self .longitude *= - 1.0
214+
215+ time_utc = int (_parse_int (data [4 ]))
191216 if time_utc is not None :
192217 hours = time_utc // 10000
193218 mins = (time_utc // 100 ) % 100
@@ -199,23 +224,10 @@ def _parse_gpgga(self, args):
199224 self .timestamp_utc .tm_mday , hours , mins , secs , 0 , 0 , - 1 ))
200225 else :
201226 self .timestamp_utc = time .struct_time ((0 , 0 , 0 , hours , mins ,
202- secs , 0 , 0 , - 1 ))
203- # Parse latitude and longitude.
204- self .latitude = _parse_degrees (data [1 ])
205- if self .latitude is not None and \
206- data [2 ] is not None and data [2 ].lower () == 's' :
207- self .latitude *= - 1.0
208- self .longitude = _parse_degrees (data [3 ])
209- if self .longitude is not None and \
210- data [4 ] is not None and data [4 ].lower () == 'w' :
211- self .longitude *= - 1.0
212- # Parse out fix quality and other simple numeric values.
213- self .fix_quality = _parse_int (data [5 ])
214- self .satellites = _parse_int (data [6 ])
215- self .horizontal_dilution = _parse_float (data [7 ])
216- self .altitude_m = _parse_float (data [8 ])
217- self .height_geoid = _parse_float (data [10 ])
218-
227+ secs , 0 , 0 , - 1 ))
228+ # Parse data active or void
229+ self .isactivedata = _parse_str (data [5 ])
230+
219231 def _parse_gprmc (self , args ):
220232 # Parse the arguments (everything after data type) for NMEA GPRMC
221233 # minimum location fix sentence.
@@ -275,43 +287,111 @@ def _parse_gprmc(self, args):
275287 self .timestamp_utc = time .struct_time ((year , month , day , 0 , 0 ,
276288 0 , 0 , 0 , - 1 ))
277289
290+ def _parse_gpvtg (self , args ):
291+ data = args .split (',' )
292+
293+ # Parse true track made good (degrees)
294+ self .true_track = _parse_float (data [0 ])
295+
296+ # Parse magnetic track made good
297+ self .mag_track = _parse_float (data [2 ])
298+
299+ # Parse speed
300+ self .speed_knots = _parse_float (data [4 ])
301+ self .speed_kmh = _parse_float (data [6 ])
302+
303+ def _parse_gpgga (self , args ):
304+ # Parse the arguments (everything after data type) for NMEA GPGGA
305+ # 3D location fix sentence.
306+ data = args .split (',' )
307+ if data is None or len (data ) != 14 :
308+ return # Unexpected number of params.
309+ # Parse fix time.
310+ time_utc = int (_parse_float (data [0 ]))
311+ if time_utc is not None :
312+ hours = time_utc // 10000
313+ mins = (time_utc // 100 ) % 100
314+ secs = time_utc % 100
315+ # Set or update time to a friendly python time struct.
316+ if self .timestamp_utc is not None :
317+ self .timestamp_utc = time .struct_time ((
318+ self .timestamp_utc .tm_year , self .timestamp_utc .tm_mon ,
319+ self .timestamp_utc .tm_mday , hours , mins , secs , 0 , 0 , - 1 ))
320+ else :
321+ self .timestamp_utc = time .struct_time ((0 , 0 , 0 , hours , mins ,
322+ secs , 0 , 0 , - 1 ))
323+ # Parse latitude and longitude.
324+ self .latitude = _parse_degrees (data [1 ])
325+ if self .latitude is not None and \
326+ data [2 ] is not None and data [2 ].lower () == 's' :
327+ self .latitude *= - 1.0
328+ self .longitude = _parse_degrees (data [3 ])
329+ if self .longitude is not None and \
330+ data [4 ] is not None and data [4 ].lower () == 'w' :
331+ self .longitude *= - 1.0
332+ # Parse out fix quality and other simple numeric values.
333+ self .fix_quality = _parse_int (data [5 ])
334+ self .satellites = _parse_int (data [6 ])
335+ self .horizontal_dilution = _parse_float (data [7 ])
336+ self .altitude_m = _parse_float (data [8 ])
337+ self .height_geoid = _parse_float (data [10 ])
338+
339+ def _parse_gpgsa (self , args ):
340+ data = args .split (',' )
341+ if data is None :
342+ return # Unexpected number of params
343+
344+ # Parse selection mode
345+ self .sel_mode = _parse_str (data [0 ])
346+ # Parse 3d fix
347+ self .fix_quality_3d = _parse_int (data [1 ])
348+ sats = list (filter (None , data [2 :- 4 ]))
349+ satdict = {}
350+ for i in range (len (sats )):
351+ satdict ["self.gps{}" .format (i )] = _parse_int (sats [i ])
352+
353+ globals ().update (satdict )
354+
355+ # Parse PDOP, dilution of precision
356+ self .pdop = _parse_float (data [- 3 ])
357+ # Parse HDOP, horizontal dilution of precision
358+ self .hdop = _parse_float (data [- 2 ])
359+ # Parse VDOP, vertical dilution of precision
360+ self .vdop = _parse_float (data [- 1 ])
361+
278362 def _parse_gpgsv (self , args ):
279363 # Parse the arguments (everything after data type) for NMEA GPGGA
280364 # 3D location fix sentence.
281365 data = args .split (',' )
282366 if data is None :
283367 return # Unexpected number of params.
368+
284369 # Parse number of messages
285370 self .total_mess_num = _parse_int (data [0 ]) # Total number of messages
286371 # Parse message number
287372 self .mess_num = _parse_int (data [1 ]) # Message number
288373 # Parse number of satellites in view
289374 self .satellites = _parse_int (data [2 ]) # Number of satellites
290- """
291- if self.satellites > 0:
292- self.has_fix = 1
293- """
375+ try :
376+ satlist
377+ except NameError :
378+ satlist = [None ] * self .total_mess_num
379+
380+ sat_tup = data [3 :]
294381
295- sats = data [3 :]
296382 satdict = {}
297- for i in range (len (sats ) / 4 ):
383+ for i in range (len (sat_tup ) / 4 ):
298384 j = i * 4
299- key = "self. gps{}" .format (i )
300- satnum = _parse_int (sats [0 + j ]) # Satellite number
301- satdeg = _parse_int (sats [1 + j ]) # Elevation in degrees
302- satazim = _parse_int (sats [2 + j ]) # Azimuth in degrees
303- satsnr = _parse_int (sats [3 + j ]) # SNR (signal-to-noise ratio) in dB
385+ key = "gps{}" .format (i )
386+ satnum = _parse_int (sat_tup [0 + j ]) # Satellite number
387+ satdeg = _parse_int (sat_tup [1 + j ]) # Elevation in degrees
388+ satazim = _parse_int (sat_tup [2 + j ]) # Azimuth in degrees
389+ satsnr = _parse_int (sat_tup [3 + j ]) # SNR (signal-to-noise ratio) in dB
304390 value = (satnum , satdeg , satazim , satsnr )
305391 satdict [key ] = value
306- """
307- params = {'self': self}
308- for k, v in satdict.items():
309- exec("%s=%s" % (k, v), params, params)
310- """
311- globals ().update (satdict )
312- # Should be self.gps0, self.gps1, self.gps2, etc
313- # Each should be a tuple with 4 values
314- # gpsx[0] = satellite number
315- # gpsx[1] = elevation in degrees
316- # gpsx[2] = azimuth in degrees to true
317- # gpsx[3] = Signal-to-noise ratio in dB
392+
393+ satlist [self .mess_num - 1 ] = satdict
394+ satlist = list (filter (None , satlist ))
395+ self .sats = {}
396+ for satdict in satlist :
397+ self .sats .update (satdict )
0 commit comments