root / trunk / twisted / protocols / gps / nmea.py

Revision 24441, 7.7 kB (checked in by thijs, 1 year ago)

Merge maintainer-email-2438: Get rid of references to maintainer email addresses from code.

Author: thijs
Reviewer: exarkun
Fixes: #2438

Line 
1 # -*- test-case-name: twisted.test.test_nmea -*-
2 # Copyright (c) 2001-2004 Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5 """NMEA 0183 implementation
6
7 Maintainer: Bob Ippolito
8
9 The following NMEA 0183 sentences are currently understood::
10     GPGGA (fix)
11     GPGLL (position)
12     GPRMC (position and time)
13     GPGSA (active satellites)
14  
15 The following NMEA 0183 sentences require implementation::
16     None really, the others aren't generally useful or implemented in most devices anyhow
17
18 Other desired features::
19     - A NMEA 0183 producer to emulate GPS devices (?)
20 """
21
22 import operator
23 from twisted.protocols import basic
24
25 POSFIX_INVALID, POSFIX_SPS, POSFIX_DGPS, POSFIX_PPS = 0, 1, 2, 3
26 MODE_AUTO, MODE_FORCED = 'A', 'M'
27 MODE_NOFIX, MODE_2D, MODE_3D = 1, 2, 3
28
29 class InvalidSentence(Exception):
30     pass
31
32 class InvalidChecksum(Exception):
33     pass
34
35 class NMEAReceiver(basic.LineReceiver):
36     """This parses most common NMEA-0183 messages, presumably from a serial GPS device at 4800 bps
37     """
38     delimiter = '\r\n'
39     dispatch = {
40         'GPGGA': 'fix',
41         'GPGLL': 'position',
42         'GPGSA': 'activesatellites',
43         'GPRMC': 'positiontime',
44         'GPGSV': 'viewsatellites',    # not implemented
45         'GPVTG': 'course',            # not implemented
46         'GPALM': 'almanac',           # not implemented
47         'GPGRS': 'range',             # not implemented
48         'GPGST': 'noise',             # not implemented
49         'GPMSS': 'beacon',            # not implemented
50         'GPZDA': 'time',              # not implemented
51     }
52     # generally you may miss the beginning of the first message
53     ignore_invalid_sentence = 1
54     # checksums shouldn't be invalid
55     ignore_checksum_mismatch = 0
56     # ignore unknown sentence types
57     ignore_unknown_sentencetypes = 0
58     # do we want to even bother checking to see if it's from the 20th century?
59     convert_dates_before_y2k = 1
60
61     def lineReceived(self, line):
62         if not line.startswith('$'):
63             if self.ignore_invalid_sentence:
64                 return
65             raise InvalidSentence("%r does not begin with $" % (line,))
66         # message is everything between $ and *, checksum is xor of all ASCII values of the message
67         strmessage, checksum = line[1:].strip().split('*')
68         message = strmessage.split(',')
69         sentencetype, message = message[0], message[1:]
70         dispatch = self.dispatch.get(sentencetype, None)
71         if (not dispatch) and (not self.ignore_unknown_sentencetypes):
72             raise InvalidSentence("sentencetype %r" % (sentencetype,))
73         if not self.ignore_checksum_mismatch:
74             checksum, calculated_checksum = int(checksum, 16), reduce(operator.xor, map(ord, strmessage))
75             if checksum != calculated_checksum:
76                 raise InvalidChecksum("Given 0x%02X != 0x%02X" % (checksum, calculated_checksum))
77         handler = getattr(self, "handle_%s" % dispatch, None)
78         decoder = getattr(self, "decode_%s" % dispatch, None)
79         if not (dispatch and handler and decoder):
80             # missing dispatch, handler, or decoder
81             return
82         # return handler(*decoder(*message))
83         try:
84             decoded = decoder(*message)
85         except Exception, e:
86             raise InvalidSentence("%r is not a valid %s (%s) sentence" % (line, sentencetype, dispatch))
87         return handler(*decoded)
88
89     def decode_position(self, latitude, ns, longitude, ew, utc, status):
90         latitude, longitude = self._decode_latlon(latitude, ns, longitude, ew)
91         utc = self._decode_utc(utc)
92         if status == 'A':
93             status = 1
94         else:
95             status = 0
96         return (
97             latitude,
98             longitude,
99             utc,
100             status,
101         )
102
103     def decode_positiontime(self, utc, status, latitude, ns, longitude, ew, speed, course, utcdate, magvar, magdir):
104         utc = self._decode_utc(utc)
105         latitude, longitude = self._decode_latlon(latitude, ns, longitude, ew)
106         if speed != '':
107             speed = float(speed)
108         else:
109             speed = None
110         if course != '':
111             course = float(course)
112         else:
113             course = None
114         utcdate = 2000+int(utcdate[4:6]), int(utcdate[2:4]), int(utcdate[0:2])
115         if self.convert_dates_before_y2k and utcdate[0] > 2073:
116             # GPS was invented by the US DoD in 1973, but NMEA uses 2 digit year.
117             # Highly unlikely that we'll be using NMEA or this twisted module in 70 years,
118             # but remotely possible that you'll be using it to play back data from the 20th century.
119             utcdate = (utcdate[0] - 100, utcdate[1], utcdate[2])
120         if magvar != '':
121             magvar = float(magvar)
122         if magdir == 'W':
123             magvar = -magvar
124         else:
125             magvar = None
126         return (
127             latitude,
128             longitude,
129             speed,
130             course,
131             # UTC seconds past utcdate
132             utc,
133             # UTC (year, month, day)
134             utcdate,
135             # None or magnetic variation in degrees (west is negative)
136             magvar,
137         )
138
139     def _decode_utc(self, utc):
140         utc_hh, utc_mm, utc_ss = map(float, (utc[:2], utc[2:4], utc[4:]))
141         return utc_hh * 3600.0 + utc_mm * 60.0 + utc_ss
142
143     def _decode_latlon(self, latitude, ns, longitude, ew):
144         latitude = float(latitude[:2]) + float(latitude[2:])/60.0
145         if ns == 'S':
146             latitude = -latitude
147         longitude = float(longitude[:3]) + float(longitude[3:])/60.0
148         if ew == 'W':
149             longitude = -longitude
150         return (latitude, longitude)
151
152     def decode_activesatellites(self, mode1, mode2, *args):
153         satellites, (pdop, hdop, vdop) = args[:12], map(float, args[12:])
154         satlist = []
155         for n in satellites:
156             if n:
157                 satlist.append(int(n))
158             else:
159                 satlist.append(None)
160         mode = (mode1, int(mode2))
161         return (
162             # satellite list by channel
163             tuple(satlist),
164             # (MODE_AUTO/MODE_FORCED, MODE_NOFIX/MODE_2DFIX/MODE_3DFIX)
165             mode,
166             # position dilution of precision
167             pdop,
168             # horizontal dilution of precision
169             hdop,
170             # vertical dilution of precision
171             vdop,
172         )
173    
174     def decode_fix(self, utc, latitude, ns, longitude, ew, posfix, satellites, hdop, altitude, altitude_units, geoid_separation, geoid_separation_units, dgps_age, dgps_station_id):
175         latitude, longitude = self._decode_latlon(latitude, ns, longitude, ew)
176         utc = self._decode_utc(utc)
177         posfix = int(posfix)
178         satellites = int(satellites)
179         hdop = float(hdop)
180         altitude = (float(altitude), altitude_units)
181         if geoid_separation != '':
182             geoid = (float(geoid_separation), geoid_separation_units)
183         else:
184             geoid = None
185         if dgps_age != '':
186             dgps = (float(dgps_age), dgps_station_id)
187         else:
188             dgps = None
189         return (
190             # seconds since 00:00 UTC
191             utc,                 
192             # latitude (degrees)
193             latitude,       
194             # longitude (degrees)
195             longitude,     
196             # position fix status (POSFIX_INVALID, POSFIX_SPS, POSFIX_DGPS, POSFIX_PPS)
197             posfix,           
198             # number of satellites used for fix 0 <= satellites <= 12
199             satellites,   
200             # horizontal dilution of precision
201             hdop,               
202             # None or (altitude according to WGS-84 ellipsoid, units (typically 'M' for meters))
203             altitude,
204             # None or (geoid separation according to WGS-84 ellipsoid, units (typically 'M' for meters))
205             geoid,
206             # (age of dgps data in seconds, dgps station id)
207             dgps,
208         )
Note: See TracBrowser for help on using the browser.