| OLD | NEW |
| (Empty) |
| 1 # Copyright (c) 2001-2004 Twisted Matrix Laboratories. | |
| 2 # See LICENSE for details. | |
| 3 | |
| 4 | |
| 5 """Rockwell Semiconductor Zodiac Serial Protocol | |
| 6 Coded from official protocol specs (Order No. GPS-25, 09/24/1996, Revision 11) | |
| 7 | |
| 8 Maintainer: U{Bob Ippolito<mailto:bob@redivi.com>} | |
| 9 | |
| 10 The following Rockwell Zodiac messages are currently understood:: | |
| 11 EARTHA\\r\\n (a hack to "turn on" a DeLorme Earthmate) | |
| 12 1000 (Geodesic Position Status Output) | |
| 13 1002 (Channel Summary) | |
| 14 1003 (Visible Satellites) | |
| 15 1011 (Receiver ID) | |
| 16 | |
| 17 The following Rockwell Zodiac messages require implementation:: | |
| 18 None really, the others aren't quite so useful and require bidirectional com
munication w/ the device | |
| 19 | |
| 20 Other desired features:: | |
| 21 - Compatability with the DeLorme Tripmate and other devices with this chipse
t (?) | |
| 22 """ | |
| 23 | |
| 24 import struct, operator, math | |
| 25 from twisted.internet import protocol | |
| 26 from twisted.python import log | |
| 27 | |
| 28 DEBUG = 1 | |
| 29 | |
| 30 class ZodiacParseError(ValueError): | |
| 31 pass | |
| 32 | |
| 33 class Zodiac(protocol.Protocol): | |
| 34 dispatch = { | |
| 35 # Output Messages (* means they get sent by the receiver by default periodic
ally) | |
| 36 1000: 'fix', # *Geodesic Position Status Output | |
| 37 1001: 'ecef', # ECEF Position Status Output | |
| 38 1002: 'channels', # *Channel Summary | |
| 39 1003: 'satellites', # *Visible Satellites | |
| 40 1005: 'dgps', # Differential GPS Status | |
| 41 1007: 'channelmeas', # Channel Measurement | |
| 42 1011: 'id', # *Receiver ID | |
| 43 1012: 'usersettings', # User-Settings Output | |
| 44 1100: 'testresults', # Built-In Test Results | |
| 45 1102: 'meastimemark', # Measurement Time Mark | |
| 46 1108: 'utctimemark', # UTC Time Mark Pulse Output | |
| 47 1130: 'serial', # Serial Port Communication Parameters In Use | |
| 48 1135: 'eepromupdate', # EEPROM Update | |
| 49 1136: 'eepromstatus', # EEPROM Status | |
| 50 } | |
| 51 # these aren't used for anything yet, just sitting here for reference | |
| 52 messages = { | |
| 53 # Input Messages | |
| 54 'fix': 1200, # Geodesic Position and Velocity Initialization | |
| 55 'udatum': 1210, # User-Defined Datum Definition | |
| 56 'mdatum': 1211, # Map Datum Select | |
| 57 'smask': 1212, # Satellite Elevation Mask Control | |
| 58 'sselect': 1213, # Satellite Candidate Select | |
| 59 'dgpsc': 1214, # Differential GPS Control | |
| 60 'startc': 1216, # Cold Start Control | |
| 61 'svalid': 1217, # Solution Validity Control | |
| 62 'antenna': 1218, # Antenna Type Select | |
| 63 'altinput': 1219, # User-Entered Altitude Input | |
| 64 'appctl': 1220, # Application Platform Control | |
| 65 'navcfg': 1221, # Nav Configuration | |
| 66 'test': 1300, # Perform Built-In Test Command | |
| 67 'restart': 1303, # Restart Command | |
| 68 'serial': 1330, # Serial Port Communications Parameters | |
| 69 'msgctl': 1331, # Message Protocol Control | |
| 70 'dgpsd': 1351, # Raw DGPS RTCM SC-104 Data | |
| 71 } | |
| 72 MAX_LENGTH = 296 | |
| 73 allow_earthmate_hack = 1 | |
| 74 recvd = "" | |
| 75 | |
| 76 def dataReceived(self, recd): | |
| 77 self.recvd = self.recvd + recd | |
| 78 while len(self.recvd) >= 10: | |
| 79 | |
| 80 # hack for DeLorme EarthMate | |
| 81 if self.recvd[:8] == 'EARTHA\r\n': | |
| 82 if self.allow_earthmate_hack: | |
| 83 self.allow_earthmate_hack = 0 | |
| 84 self.transport.write('EARTHA\r\n') | |
| 85 self.recvd = self.recvd[8:] | |
| 86 continue | |
| 87 | |
| 88 if self.recvd[0:2] != '\xFF\x81': | |
| 89 if DEBUG: | |
| 90 raise ZodiacParseError('Invalid Sync %r' % self.recvd) | |
| 91 else: | |
| 92 raise ZodiacParseError | |
| 93 sync, msg_id, length, acknak, checksum = struct.unpack('<HHHHh', self.recv
d[:10]) | |
| 94 | |
| 95 # verify checksum | |
| 96 cksum = -(reduce(operator.add, (sync, msg_id, length, acknak)) & 0xFFFF) | |
| 97 cksum, = struct.unpack('<h', struct.pack('<h', cksum)) | |
| 98 if cksum != checksum: | |
| 99 if DEBUG: | |
| 100 raise ZodiacParseError('Invalid Header Checksum %r != %r %r' % (checks
um, cksum, self.recvd[:8])) | |
| 101 else: | |
| 102 raise ZodiacParseError | |
| 103 | |
| 104 # length was in words, now it's bytes | |
| 105 length = length * 2 | |
| 106 | |
| 107 # do we need more data ? | |
| 108 neededBytes = 10 | |
| 109 if length: | |
| 110 neededBytes += length + 2 | |
| 111 if len(self.recvd) < neededBytes: | |
| 112 break | |
| 113 | |
| 114 if neededBytes > self.MAX_LENGTH: | |
| 115 raise ZodiacParseError("Invalid Header??") | |
| 116 | |
| 117 # empty messages pass empty strings | |
| 118 message = '' | |
| 119 | |
| 120 # does this message have data ? | |
| 121 if length: | |
| 122 message, checksum = self.recvd[10:10+length], struct.unpack('<h', self.r
ecvd[10+length:neededBytes])[0] | |
| 123 cksum = 0x10000 - (reduce(operator.add, struct.unpack('<%dH' % (length/2
), message)) & 0xFFFF) | |
| 124 cksum, = struct.unpack('<h', struct.pack('<h', cksum)) | |
| 125 if cksum != checksum: | |
| 126 if DEBUG: | |
| 127 log.dmsg('msg_id = %r length = %r' % (msg_id, length), debug=True) | |
| 128 raise ZodiacParseError('Invalid Data Checksum %r != %r %r' % (checks
um, cksum, message)) | |
| 129 else: | |
| 130 raise ZodiacParseError | |
| 131 | |
| 132 # discard used buffer, dispatch message | |
| 133 self.recvd = self.recvd[neededBytes:] | |
| 134 self.receivedMessage(msg_id, message, acknak) | |
| 135 | |
| 136 def receivedMessage(self, msg_id, message, acknak): | |
| 137 dispatch = self.dispatch.get(msg_id, None) | |
| 138 if not dispatch: | |
| 139 raise ZodiacParseError('Unknown msg_id = %r' % msg_id) | |
| 140 handler = getattr(self, 'handle_%s' % dispatch, None) | |
| 141 decoder = getattr(self, 'decode_%s' % dispatch, None) | |
| 142 if not (handler and decoder): | |
| 143 # missing handler or decoder | |
| 144 #if DEBUG: | |
| 145 # log.msg('MISSING HANDLER/DECODER PAIR FOR: %r' % (dispatch,), debug=Tru
e) | |
| 146 return | |
| 147 decoded = decoder(message) | |
| 148 return handler(*decoded) | |
| 149 | |
| 150 def decode_fix(self, message): | |
| 151 assert len(message) == 98, "Geodesic Position Status Output should be 55 wor
ds total (98 byte message)" | |
| 152 (ticks, msgseq, satseq, navstatus, navtype, nmeasure, polar, gpswk, gpses, g
psns, utcdy, utcmo, utcyr, utchr, utcmn, utcsc, utcns, latitude, longitude, heig
ht, geoidalsep, speed, course, magvar, climb, mapdatum, exhposerr, exvposerr, ex
timeerr, exphvelerr, clkbias, clkbiasdev, clkdrift, clkdriftdev) = struct.unpack
('<LhhHHHHHLLHHHHHHLlllhLHhhHLLLHllll', message) | |
| 153 | |
| 154 # there's a lot of shit in here.. | |
| 155 # I'll just snag the important stuff and spit it out like my NMEA decoder | |
| 156 utc = (utchr * 3600.0) + (utcmn * 60.0) + utcsc + (float(utcns) * 0.00000000
1) | |
| 157 | |
| 158 log.msg('utchr, utcmn, utcsc, utcns = ' + repr((utchr, utcmn, utcsc, utcns))
, debug=True) | |
| 159 | |
| 160 latitude = float(latitude) * 0.00000180 / math.pi | |
| 161 longitude = float(longitude) * 0.00000180 / math.pi | |
| 162 posfix = not (navstatus & 0x001c) | |
| 163 satellites = nmeasure | |
| 164 hdop = float(exhposerr) * 0.01 | |
| 165 altitude = float(height) * 0.01, 'M' | |
| 166 geoid = float(geoidalsep) * 0.01, 'M' | |
| 167 dgps = None | |
| 168 return ( | |
| 169 # seconds since 00:00 UTC | |
| 170 utc, | |
| 171 # latitude (degrees) | |
| 172 latitude, | |
| 173 # longitude (degrees) | |
| 174 longitude, | |
| 175 # position fix status (invalid = False, valid = True) | |
| 176 posfix, | |
| 177 # number of satellites [measurements] used for fix 0 <= satellites <= 12 | |
| 178 satellites, | |
| 179 # horizontal dilution of precision | |
| 180 hdop, | |
| 181 # (altitude according to WGS-84 ellipsoid, units (always 'M' for meters)) | |
| 182 altitude, | |
| 183 # (geoid separation according to WGS-84 ellipsoid, units (always 'M' for m
eters)) | |
| 184 geoid, | |
| 185 # None, for compatability w/ NMEA code | |
| 186 dgps, | |
| 187 ) | |
| 188 | |
| 189 def decode_id(self, message): | |
| 190 assert len(message) == 106, "Receiver ID Message should be 59 words total (1
06 byte message)" | |
| 191 ticks, msgseq, channels, software_version, software_date, options_list, rese
rved = struct.unpack('<Lh20s20s20s20s20s', message) | |
| 192 channels, software_version, software_date, options_list = map(lambda s: s.sp
lit('\0')[0], (channels, software_version, software_date, options_list)) | |
| 193 software_version = float(software_version) | |
| 194 channels = int(channels) # 0-12 .. but ALWAYS 12, so we ignore. | |
| 195 options_list = int(options_list[:4], 16) # only two bitflags, others are res
erved | |
| 196 minimize_rom = (options_list & 0x01) > 0 | |
| 197 minimize_ram = (options_list & 0x02) > 0 | |
| 198 # (version info), (options info) | |
| 199 return ((software_version, software_date), (minimize_rom, minimize_ram)) | |
| 200 | |
| 201 def decode_channels(self, message): | |
| 202 assert len(message) == 90, "Channel Summary Message should be 51 words total
(90 byte message)" | |
| 203 ticks, msgseq, satseq, gpswk, gpsws, gpsns = struct.unpack('<LhhHLL', messag
e[:18]) | |
| 204 channels = [] | |
| 205 message = message[18:] | |
| 206 for i in range(12): | |
| 207 flags, prn, cno = struct.unpack('<HHH', message[6 * i:6 * (i + 1)]) | |
| 208 # measurement used, ephemeris available, measurement valid, dgps correctio
ns available | |
| 209 flags = (flags & 0x01, flags & 0x02, flags & 0x04, flags & 0x08) | |
| 210 channels.append((flags, prn, cno)) | |
| 211 # ((flags, satellite PRN, C/No in dbHz)) for 12 channels | |
| 212 # satellite message sequence number | |
| 213 # gps week number, gps seconds in week (??), gps nanoseconds from Epoch | |
| 214 return (tuple(channels),) #, satseq, (gpswk, gpsws, gpsns)) | |
| 215 | |
| 216 def decode_satellites(self, message): | |
| 217 assert len(message) == 90, "Visible Satellites Message should be 51 words to
tal (90 byte message)" | |
| 218 ticks, msgseq, gdop, pdop, hdop, vdop, tdop, numsatellites = struct.unpack('
<LhhhhhhH', message[:18]) | |
| 219 gdop, pdop, hdop, vdop, tdop = map(lambda n: float(n) * 0.01, (gdop, pdop, h
dop, vdop, tdop)) | |
| 220 satellites = [] | |
| 221 message = message[18:] | |
| 222 for i in range(numsatellites): | |
| 223 prn, azi, elev = struct.unpack('<Hhh', message[6 * i:6 * (i + 1)]) | |
| 224 azi, elev = map(lambda n: (float(n) * 0.0180 / math.pi), (azi, elev)) | |
| 225 satellites.push((prn, azi, elev)) | |
| 226 # ((PRN [0, 32], azimuth +=[0.0, 180.0] deg, elevation +-[0.0, 90.0] deg)) s
atellite info (0-12) | |
| 227 # (geometric, position, horizontal, vertical, time) dilution of precision | |
| 228 return (tuple(satellites), (gdop, pdop, hdop, vdop, tdop)) | |
| 229 | |
| 230 def decode_dgps(self, message): | |
| 231 assert len(message) == 38, "Differential GPS Status Message should be 25 wor
ds total (38 byte message)" | |
| 232 raise NotImplementedError | |
| 233 | |
| 234 def decode_ecef(self, message): | |
| 235 assert len(message) == 96, "ECEF Position Status Output Message should be 54
words total (96 byte message)" | |
| 236 raise NotImplementedError | |
| 237 | |
| 238 def decode_channelmeas(self, message): | |
| 239 assert len(message) == 296, "Channel Measurement Message should be 154 words
total (296 byte message)" | |
| 240 raise NotImplementedError | |
| 241 | |
| 242 def decode_usersettings(self, message): | |
| 243 assert len(message) == 32, "User-Settings Output Message should be 22 words
total (32 byte message)" | |
| 244 raise NotImplementedError | |
| 245 | |
| 246 def decode_testresults(self, message): | |
| 247 assert len(message) == 28, "Built-In Test Results Message should be 20 words
total (28 byte message)" | |
| 248 raise NotImplementedError | |
| 249 | |
| 250 def decode_meastimemark(self, message): | |
| 251 assert len(message) == 494, "Measurement Time Mark Message should be 253 wor
ds total (494 byte message)" | |
| 252 raise NotImplementedError | |
| 253 | |
| 254 def decode_utctimemark(self, message): | |
| 255 assert len(message) == 28, "UTC Time Mark Pulse Output Message should be 20
words total (28 byte message)" | |
| 256 raise NotImplementedError | |
| 257 | |
| 258 def decode_serial(self, message): | |
| 259 assert len(message) == 30, "Serial Port Communication Paramaters In Use Mess
age should be 21 words total (30 byte message)" | |
| 260 raise NotImplementedError | |
| 261 | |
| 262 def decode_eepromupdate(self, message): | |
| 263 assert len(message) == 8, "EEPROM Update Message should be 10 words total (8
byte message)" | |
| 264 raise NotImplementedError | |
| 265 | |
| 266 def decode_eepromstatus(self, message): | |
| 267 assert len(message) == 24, "EEPROM Status Message should be 18 words total (
24 byte message)" | |
| 268 raise NotImplementedError | |
| OLD | NEW |