1
# Twisted, the Framework of Your Internet
2
# Copyright (C) 2001-2002 Matthew W. Lefkowitz
4
# This library is free software; you can redistribute it and/or
5
# modify it under the terms of version 2.1 of the GNU Lesser General Public
6
# License as published by the Free Software Foundation.
8
# This library is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
# Lesser General Public License for more details.
13
# You should have received a copy of the GNU Lesser General Public
14
# License along with this library; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""NMEA 0183 implementation
19
Maintainer: U{Bob Ippolito<mailto:bob@redivi.com>}
21
The following NMEA 0183 sentences are currently understood::
24
GPRMC (position and time)
25
GPGSA (active satellites)
27
The following NMEA 0183 sentences require implementation::
28
None really, the others aren't generally useful or implemented in most devices anyhow
30
Other desired features::
31
- A NMEA 0183 producer to emulate GPS devices (?)
35
from twisted.protocols import basic
37
POSFIX_INVALID, POSFIX_SPS, POSFIX_DGPS, POSFIX_PPS = 0, 1, 2, 3
38
MODE_AUTO, MODE_FORCED = 'A', 'M'
39
MODE_NOFIX, MODE_2D, MODE_3D = 1, 2, 3
41
class InvalidSentence(Exception):
44
class InvalidChecksum(Exception):
47
class NMEAReceiver(basic.LineReceiver):
48
"""This parses most common NMEA-0183 messages, presumably from a serial GPS device at 4800 bps
54
'GPGSA': 'activesatellites',
55
'GPRMC': 'positiontime',
56
'GPGSV': 'viewsatellites', # not implemented
57
'GPVTG': 'course', # not implemented
58
'GPALM': 'almanac', # not implemented
59
'GPGRS': 'range', # not implemented
60
'GPGST': 'noise', # not implemented
61
'GPMSS': 'beacon', # not implemented
62
'GPZDA': 'time', # not implemented
64
# generally you may miss the beginning of the first message
65
ignore_invalid_sentence = 1
66
# checksums shouldn't be invalid
67
ignore_checksum_mismatch = 0
68
# ignore unknown sentence types
69
ignore_unknown_sentencetypes = 0
70
# do we want to even bother checking to see if it's from the 20th century?
71
convert_dates_before_y2k = 1
73
def lineReceived(self, line):
74
if not line.startswith('$'):
75
if self.ignore_invalid_sentence:
77
raise InvalidSentence("%r does not begin with $" % (line,))
78
# message is everything between $ and *, checksum is xor of all ASCII values of the message
79
strmessage, checksum = line[1:].strip().split('*')
80
message = strmessage.split(',')
81
sentencetype, message = message[0], message[1:]
82
dispatch = self.dispatch.get(sentencetype, None)
83
if (not dispatch) and (not self.ignore_unknown_sentencetypes):
84
raise InvalidSentence("sentencetype %r" % (sentencetype,))
85
if not self.ignore_checksum_mismatch:
86
checksum, calculated_checksum = int(checksum, 16), reduce(operator.xor, map(ord, strmessage))
87
if checksum != calculated_checksum:
88
raise InvalidChecksum("Given 0x%02X != 0x%02X" % (checksum, calculated_checksum))
89
handler = getattr(self, "handle_%s" % dispatch, None)
90
decoder = getattr(self, "decode_%s" % dispatch, None)
91
if not (dispatch and handler and decoder):
92
# missing dispatch, handler, or decoder
94
# return handler(*decoder(*message))
96
decoded = decoder(*message)
98
raise InvalidSentence("%r is not a valid %s (%s) sentence" % (line, sentencetype, dispatch))
99
return handler(*decoded)
101
def decode_position(self, latitude, ns, longitude, ew, utc, status):
102
latitude, longitude = self._decode_latlon(latitude, ns, longitude, ew)
103
utc = self._decode_utc(utc)
115
def decode_positiontime(self, utc, status, latitude, ns, longitude, ew, speed, course, utcdate, magvar, magdir):
116
utc = self._decode_utc(utc)
117
latitude, longitude = self._decode_latlon(latitude, ns, longitude, ew)
123
course = float(course)
126
utcdate = 2000+int(utcdate[4:6]), int(utcdate[2:4]), int(utcdate[0:2])
127
if self.convert_dates_before_y2k and utcdate[0] > 2073:
128
# GPS was invented by the US DoD in 1973, but NMEA uses 2 digit year.
129
# Highly unlikely that we'll be using NMEA or this twisted module in 70 years,
130
# but remotely possible that you'll be using it to play back data from the 20th century.
131
utcdate = (utcdate[0] - 100, utcdate[1], utcdate[2])
133
magvar = float(magvar)
143
# UTC seconds past utcdate
145
# UTC (year, month, day)
147
# None or magnetic variation in degrees (west is negative)
151
def _decode_utc(self, utc):
152
utc_hh, utc_mm, utc_ss = map(float, (utc[:2], utc[2:4], utc[4:]))
153
return utc_hh * 3600.0 + utc_mm * 60.0 + utc_ss
155
def _decode_latlon(self, latitude, ns, longitude, ew):
156
latitude = float(latitude[:2]) + float(latitude[2:])/60.0
159
longitude = float(longitude[:3]) + float(longitude[3:])/60.0
161
longitude = -longitude
162
return (latitude, longitude)
164
def decode_activesatellites(self, mode1, mode2, *args):
165
satellites, (pdop, hdop, vdop) = args[:12], map(float, args[12:])
169
satlist.append(int(n))
172
mode = (mode1, int(mode2))
174
# satellite list by channel
176
# (MODE_AUTO/MODE_FORCED, MODE_NOFIX/MODE_2DFIX/MODE_3DFIX)
178
# position dilution of precision
180
# horizontal dilution of precision
182
# vertical dilution of precision
186
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):
187
latitude, longitude = self._decode_latlon(latitude, ns, longitude, ew)
188
utc = self._decode_utc(utc)
190
satellites = int(satellites)
192
altitude = (float(altitude), altitude_units)
193
if geoid_separation != '':
194
geoid = (float(geoid_separation), geoid_separation_units)
198
dgps = (float(dgps_age), dgps_station_id)
202
# seconds since 00:00 UTC
206
# longitude (degrees)
208
# position fix status (POSFIX_INVALID, POSFIX_SPS, POSFIX_DGPS, POSFIX_PPS)
210
# number of satellites used for fix 0 <= satellites <= 12
212
# horizontal dilution of precision
214
# None or (altitude according to WGS-84 ellipsoid, units (typically 'M' for meters))
216
# None or (geoid separation according to WGS-84 ellipsoid, units (typically 'M' for meters))
218
# (age of dgps data in seconds, dgps station id)