@@ -70,6 +70,11 @@ def _parse_float(nmea_data):
70
70
return None
71
71
return float (nmea_data )
72
72
73
+ def _parse_str (nmea_data ):
74
+ if nmea_data is None or nmea_data == '' :
75
+ return None
76
+ return str (nmea_data )
77
+
73
78
# lint warning about too many attributes disabled
74
79
#pylint: disable-msg=R0902
75
80
class GPS :
@@ -83,13 +88,20 @@ def __init__(self, uart, debug=False):
83
88
self .latitude = None
84
89
self .longitude = None
85
90
self .fix_quality = None
91
+ self .fix_quality_3d = None
86
92
self .satellites = None
87
93
self .horizontal_dilution = None
88
94
self .altitude_m = None
89
95
self .height_geoid = None
90
- self .velocity_knots = None
91
96
self .speed_knots = None
97
+ self .speed_kmh = None
92
98
self .track_angle_deg = None
99
+ self .total_mess_num = None
100
+ self .mess_num = None
101
+ self .sats = None
102
+ self .isactivedata = None
103
+ self .true_track = None
104
+ self .mag_track = None
93
105
self .debug = debug
94
106
95
107
def update (self ):
@@ -102,17 +114,27 @@ def update(self):
102
114
try :
103
115
sentence = self ._parse_sentence ()
104
116
except UnicodeError :
117
+ print ("UnicodeError" )
105
118
return None
106
119
if sentence is None :
107
120
return False
108
121
if self .debug :
109
122
print (sentence )
110
123
data_type , args = sentence
111
124
data_type = bytes (data_type .upper (), "ascii" )
112
- if data_type == b'GPGGA' : # GGA, 3d location fix
113
- self ._parse_gpgga (args )
114
- elif data_type == b'GPRMC' : # RMC, minimum location info
125
+ #return sentence
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
115
129
self ._parse_gprmc (args )
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
137
+ self ._parse_gpgsv (args )
116
138
return True
117
139
118
140
def send_command (self , command , add_checksum = True ):
@@ -135,6 +157,10 @@ def send_command(self, command, add_checksum=True):
135
157
def has_fix (self ):
136
158
"""True if a current fix for location information is available."""
137
159
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
138
164
139
165
@property
140
166
def datetime (self ):
@@ -147,15 +173,19 @@ def _parse_sentence(self):
147
173
# This needs to be refactored when it can be tested.
148
174
149
175
# Only continue if we have at least 64 bytes in the input buffer
176
+ """
150
177
if self._uart.in_waiting < 64:
151
178
return None
179
+ """
152
180
153
181
sentence = self ._uart .readline ()
154
182
if sentence is None or sentence == b'' or len (sentence ) < 1 :
183
+ print ("Sentence is none" )
155
184
return None
156
185
try :
157
186
sentence = str (sentence , 'ascii' ).strip ()
158
187
except UnicodeError :
188
+ print ("UnicodeError" )
159
189
return None
160
190
# Look for a checksum and validate it if present.
161
191
if len (sentence ) > 7 and sentence [- 3 ] == '*' :
@@ -165,25 +195,34 @@ def _parse_sentence(self):
165
195
for i in range (1 , len (sentence )- 3 ):
166
196
actual ^= ord (sentence [i ])
167
197
if actual != expected :
198
+ print ("Actual != expected" )
168
199
return None # Failed to validate checksum.
169
200
# Remove checksum once validated.
170
201
sentence = sentence [:- 3 ]
171
202
# Parse out the type of sentence (first string after $ up to comma)
172
203
# and then grab the rest as data within the sentence.
173
204
delineator = sentence .find (',' )
174
205
if delineator == - 1 :
206
+ print ("Bad delineator" )
175
207
return None # Invalid sentence, no comma after data type.
176
208
data_type = sentence [1 :delineator ]
177
209
return (data_type , sentence [delineator + 1 :])
178
210
179
- def _parse_gpgga (self , args ):
180
- # Parse the arguments (everything after data type) for NMEA GPGGA
181
- # 3D location fix sentence.
211
+ def _parse_gpgll (self , args ):
182
212
data = args .split (',' )
183
- if data is None or len (data ) != 14 :
184
- return # Unexpected number of params.
185
- # Parse fix time.
186
- time_utc = int (_parse_float (data [0 ]))
213
+ if data is None or data [0 ] is None :
214
+ return # Unexpected number of params.
215
+
216
+ # Parse latitude and longitude.
217
+ self .latitude = _parse_degrees (data [0 ])
218
+ if self .latitude is not None and \
219
+ data [1 ] is not None and data [1 ].lower () == 's' :
220
+ self .latitude *= - 1.0
221
+ self .longitude = _parse_degrees (data [2 ])
222
+ if self .longitude is not None and \
223
+ data [3 ] is not None and data [3 ].lower () == 'w' :
224
+ self .longitude *= - 1.0
225
+ time_utc = int (_parse_int (float (data [4 ])))
187
226
if time_utc is not None :
188
227
hours = time_utc // 10000
189
228
mins = (time_utc // 100 ) % 100
@@ -195,23 +234,10 @@ def _parse_gpgga(self, args):
195
234
self .timestamp_utc .tm_mday , hours , mins , secs , 0 , 0 , - 1 ))
196
235
else :
197
236
self .timestamp_utc = time .struct_time ((0 , 0 , 0 , hours , mins ,
198
- secs , 0 , 0 , - 1 ))
199
- # Parse latitude and longitude.
200
- self .latitude = _parse_degrees (data [1 ])
201
- if self .latitude is not None and \
202
- data [2 ] is not None and data [2 ].lower () == 's' :
203
- self .latitude *= - 1.0
204
- self .longitude = _parse_degrees (data [3 ])
205
- if self .longitude is not None and \
206
- data [4 ] is not None and data [4 ].lower () == 'w' :
207
- self .longitude *= - 1.0
208
- # Parse out fix quality and other simple numeric values.
209
- self .fix_quality = _parse_int (data [5 ])
210
- self .satellites = _parse_int (data [6 ])
211
- self .horizontal_dilution = _parse_float (data [7 ])
212
- self .altitude_m = _parse_float (data [8 ])
213
- self .height_geoid = _parse_float (data [10 ])
214
-
237
+ secs , 0 , 0 , - 1 ))
238
+ # Parse data active or void
239
+ self .isactivedata = _parse_str (data [5 ])
240
+
215
241
def _parse_gprmc (self , args ):
216
242
# Parse the arguments (everything after data type) for NMEA GPRMC
217
243
# minimum location fix sentence.
@@ -270,3 +296,113 @@ def _parse_gprmc(self, args):
270
296
# Time hasn't been set so create it.
271
297
self .timestamp_utc = time .struct_time ((year , month , day , 0 , 0 ,
272
298
0 , 0 , 0 , - 1 ))
299
+
300
+ def _parse_gpvtg (self , args ):
301
+ data = args .split (',' )
302
+
303
+ # Parse true track made good (degrees)
304
+ self .true_track = _parse_float (data [0 ])
305
+
306
+ # Parse magnetic track made good
307
+ self .mag_track = _parse_float (data [2 ])
308
+
309
+ # Parse speed
310
+ self .speed_knots = _parse_float (data [4 ])
311
+ self .speed_kmh = _parse_float (data [6 ])
312
+
313
+ def _parse_gpgga (self , args ):
314
+ # Parse the arguments (everything after data type) for NMEA GPGGA
315
+ # 3D location fix sentence.
316
+ data = args .split (',' )
317
+ if data is None or len (data ) != 14 :
318
+ return # Unexpected number of params.
319
+ # Parse fix time.
320
+ time_utc = int (_parse_float (data [0 ]))
321
+ if time_utc is not None :
322
+ hours = time_utc // 10000
323
+ mins = (time_utc // 100 ) % 100
324
+ secs = time_utc % 100
325
+ # Set or update time to a friendly python time struct.
326
+ if self .timestamp_utc is not None :
327
+ self .timestamp_utc = time .struct_time ((
328
+ self .timestamp_utc .tm_year , self .timestamp_utc .tm_mon ,
329
+ self .timestamp_utc .tm_mday , hours , mins , secs , 0 , 0 , - 1 ))
330
+ else :
331
+ self .timestamp_utc = time .struct_time ((0 , 0 , 0 , hours , mins ,
332
+ secs , 0 , 0 , - 1 ))
333
+ # Parse latitude and longitude.
334
+ self .latitude = _parse_degrees (data [1 ])
335
+ if self .latitude is not None and \
336
+ data [2 ] is not None and data [2 ].lower () == 's' :
337
+ self .latitude *= - 1.0
338
+ self .longitude = _parse_degrees (data [3 ])
339
+ if self .longitude is not None and \
340
+ data [4 ] is not None and data [4 ].lower () == 'w' :
341
+ self .longitude *= - 1.0
342
+ # Parse out fix quality and other simple numeric values.
343
+ self .fix_quality = _parse_int (data [5 ])
344
+ self .satellites = _parse_int (data [6 ])
345
+ self .horizontal_dilution = _parse_float (data [7 ])
346
+ self .altitude_m = _parse_float (data [8 ])
347
+ self .height_geoid = _parse_float (data [10 ])
348
+
349
+ def _parse_gpgsa (self , args ):
350
+ data = args .split (',' )
351
+ if data is None :
352
+ return # Unexpected number of params
353
+
354
+ # Parse selection mode
355
+ self .sel_mode = _parse_str (data [0 ])
356
+ # Parse 3d fix
357
+ self .fix_quality_3d = _parse_int (data [1 ])
358
+ sats = list (filter (None , data [2 :- 4 ]))
359
+ satdict = {}
360
+ for i in range (len (sats )):
361
+ satdict ["self.gps{}" .format (i )] = _parse_int (sats [i ])
362
+
363
+ globals ().update (satdict )
364
+
365
+ # Parse PDOP, dilution of precision
366
+ self .pdop = _parse_float (data [- 3 ])
367
+ # Parse HDOP, horizontal dilution of precision
368
+ self .hdop = _parse_float (data [- 2 ])
369
+ # Parse VDOP, vertical dilution of precision
370
+ self .vdop = _parse_float (data [- 1 ])
371
+
372
+ def _parse_gpgsv (self , args ):
373
+ # Parse the arguments (everything after data type) for NMEA GPGGA
374
+ # 3D location fix sentence.
375
+ data = args .split (',' )
376
+ if data is None :
377
+ return # Unexpected number of params.
378
+
379
+ # Parse number of messages
380
+ self .total_mess_num = _parse_int (data [0 ]) # Total number of messages
381
+ # Parse message number
382
+ self .mess_num = _parse_int (data [1 ]) # Message number
383
+ # Parse number of satellites in view
384
+ self .satellites = _parse_int (data [2 ]) # Number of satellites
385
+ try :
386
+ satlist
387
+ except NameError :
388
+ satlist = [None ] * self .total_mess_num
389
+
390
+ sat_tup = data [3 :]
391
+
392
+ satdict = {}
393
+ for i in range (len (sat_tup )/ 4 ):
394
+ j = i * 4
395
+ key = "gps{}" .format (i + (4 * (self .mess_num - 1 )))
396
+ satnum = _parse_int (sat_tup [0 + j ]) # Satellite number
397
+ satdeg = _parse_int (sat_tup [1 + j ]) # Elevation in degrees
398
+ satazim = _parse_int (sat_tup [2 + j ]) # Azimuth in degrees
399
+ satsnr = _parse_int (sat_tup [3 + j ]) # SNR (signal-to-noise ratio) in dB
400
+ value = (satnum , satdeg , satazim , satsnr )
401
+ satdict [key ] = value
402
+
403
+ satlist [self .mess_num - 1 ] = satdict
404
+ satlist = list (filter (None , satlist ))
405
+ self .sats = {}
406
+ for satdict in satlist :
407
+ self .sats .update (satdict )
408
+ print (self .sats )
0 commit comments