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

Revision 24441, 11.4 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 # 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: Bob Ippolito
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 communication w/ the device
19
20 Other desired features::
21     - Compatability with the DeLorme Tripmate and other devices with this chipset (?)
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 periodically)
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.recvd[: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' % (checksum, 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.recvd[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' % (checksum, 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=True)
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 words total (98 byte message)"
152     (ticks, msgseq, satseq, navstatus, navtype, nmeasure, polar, gpswk, gpses, gpsns, utcdy, utcmo, utcyr, utchr, utcmn, utcsc, utcns, latitude, longitude, height, geoidalsep, speed, course, magvar, climb, mapdatum, exhposerr, exvposerr, extimeerr, 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.000000001)
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 meters))
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 (106 byte message)"
191     ticks, msgseq, channels, software_version, software_date, options_list, reserved = struct.unpack('<Lh20s20s20s20s20s', message)
192     channels, software_version, software_date, options_list = map(lambda s: s.split('\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 reserved
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', message[: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 corrections 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 total (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, hdop, 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)) satellite 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 words 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 words 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 Message 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
Note: See TracBrowser for help on using the browser.