| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 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', |
|---|
| 45 |
'GPVTG': 'course', |
|---|
| 46 |
'GPALM': 'almanac', |
|---|
| 47 |
'GPGRS': 'range', |
|---|
| 48 |
'GPGST': 'noise', |
|---|
| 49 |
'GPMSS': 'beacon', |
|---|
| 50 |
'GPZDA': 'time', |
|---|
| 51 |
} |
|---|
| 52 |
|
|---|
| 53 |
ignore_invalid_sentence = 1 |
|---|
| 54 |
|
|---|
| 55 |
ignore_checksum_mismatch = 0 |
|---|
| 56 |
|
|---|
| 57 |
ignore_unknown_sentencetypes = 0 |
|---|
| 58 |
|
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 81 |
return |
|---|
| 82 |
|
|---|
| 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 |
|
|---|
| 117 |
|
|---|
| 118 |
|
|---|
| 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 |
|
|---|
| 132 |
utc, |
|---|
| 133 |
|
|---|
| 134 |
utcdate, |
|---|
| 135 |
|
|---|
| 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 |
|
|---|
| 163 |
tuple(satlist), |
|---|
| 164 |
|
|---|
| 165 |
mode, |
|---|
| 166 |
|
|---|
| 167 |
pdop, |
|---|
| 168 |
|
|---|
| 169 |
hdop, |
|---|
| 170 |
|
|---|
| 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 |
|
|---|
| 191 |
utc, |
|---|
| 192 |
|
|---|
| 193 |
latitude, |
|---|
| 194 |
|
|---|
| 195 |
longitude, |
|---|
| 196 |
|
|---|
| 197 |
posfix, |
|---|
| 198 |
|
|---|
| 199 |
satellites, |
|---|
| 200 |
|
|---|
| 201 |
hdop, |
|---|
| 202 |
|
|---|
| 203 |
altitude, |
|---|
| 204 |
|
|---|
| 205 |
geoid, |
|---|
| 206 |
|
|---|
| 207 |
dgps, |
|---|
| 208 |
) |
|---|