50
50
# Internal helper parsing functions.
51
51
# These handle input that might be none or null and return none instead of
52
52
# throwing errors.
53
+
54
+
53
55
def _parse_degrees (nmea_data ):
54
56
# Parse a NMEA lat/long data pair 'dddmm.mmmm' into a pure degrees value.
55
57
# Where ddd is the degrees, mm.mmmm is the minutes.
@@ -60,26 +62,31 @@ def _parse_degrees(nmea_data):
60
62
minutes = raw % 100
61
63
return deg + minutes / 60
62
64
65
+
63
66
def _parse_int (nmea_data ):
64
67
if nmea_data is None or nmea_data == '' :
65
68
return None
66
69
return int (nmea_data )
67
70
71
+
68
72
def _parse_float (nmea_data ):
69
73
if nmea_data is None or nmea_data == '' :
70
74
return None
71
75
return float (nmea_data )
72
76
77
+
73
78
def _parse_str (nmea_data ):
74
79
if nmea_data is None or nmea_data == '' :
75
80
return None
76
81
return str (nmea_data )
77
82
78
83
# lint warning about too many attributes disabled
79
84
#pylint: disable-msg=R0902
85
+
86
+
80
87
class 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.
83
90
"""
84
91
def __init__ (self , uart , debug = False ):
85
92
self ._uart = uart
@@ -90,13 +97,21 @@ def __init__(self, uart, debug=False):
90
97
self .fix_quality = None
91
98
self .fix_quality_3d = None
92
99
self .satellites = None
100
+ self .satellites_prev = None
93
101
self .horizontal_dilution = None
94
102
self .altitude_m = None
95
103
self .height_geoid = None
96
104
self .speed_knots = None
97
105
self .track_angle_deg = None
98
106
self .sats = None
99
107
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
100
115
self .debug = debug
101
116
102
117
def update (self ):
@@ -109,15 +124,14 @@ def update(self):
109
124
try :
110
125
sentence = self ._parse_sentence ()
111
126
except UnicodeError :
112
- print ("UnicodeError" )
113
127
return None
114
128
if sentence is None :
115
129
return False
116
130
if self .debug :
117
131
print (sentence )
118
132
data_type , args = sentence
119
133
data_type = bytes (data_type .upper (), "ascii" )
120
- #return sentence
134
+ # return sentence
121
135
if data_type == b'GPGLL' : # GLL, Geographic Position – Latitude/Longitude
122
136
self ._parse_gpgll (args )
123
137
elif data_type == b'GPRMC' : # RMC, minimum location info
@@ -149,7 +163,9 @@ def has_fix(self):
149
163
150
164
@property
151
165
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"""
153
169
return self .fix_quality_3d is not None and self .fix_quality_3d >= 2
154
170
155
171
@property
@@ -162,8 +178,8 @@ def _parse_sentence(self):
162
178
# pylint: disable=len-as-condition
163
179
# This needs to be refactored when it can be tested.
164
180
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 :
167
183
return None
168
184
169
185
sentence = self ._uart .readline ()
@@ -195,7 +211,7 @@ def _parse_sentence(self):
195
211
def _parse_gpgll (self , args ):
196
212
data = args .split (',' )
197
213
if data is None or data [0 ] is None :
198
- return # Unexpected number of params.
214
+ return # Unexpected number of params.
199
215
200
216
# Parse latitude and longitude.
201
217
self .latitude = _parse_degrees (data [0 ])
@@ -262,7 +278,7 @@ def _parse_gprmc(self, args):
262
278
if data [8 ] is not None and len (data [8 ]) == 6 :
263
279
day = int (data [8 ][0 :2 ])
264
280
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.
266
282
# This is a problem with the NMEA
267
283
# spec and not this code.
268
284
if self .timestamp_utc is not None :
@@ -315,3 +331,72 @@ def _parse_gpgga(self, args):
315
331
self .horizontal_dilution = _parse_float (data [7 ])
316
332
self .altitude_m = _parse_float (data [8 ])
317
333
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