OLD | NEW |
| (Empty) |
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: U{Bob Ippolito<mailto:bob@redivi.com>} | |
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 devic
es 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 va
lues 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.x
or, map(ord, strmessage)) | |
75 if checksum != calculated_checksum: | |
76 raise InvalidChecksum("Given 0x%02X != 0x%02X" % (checksum, calc
ulated_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, spee
d, 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 fr
om 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, h
dop, altitude, altitude_units, geoid_separation, geoid_separation_units, dgps_ag
e, 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, POSF
IX_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 (ty
pically 'M' for meters)) | |
205 geoid, | |
206 # (age of dgps data in seconds, dgps station id) | |
207 dgps, | |
208 ) | |
OLD | NEW |