"Initial commit to Gerrit"
[profile/ivi/gpsd.git] / gps / gps.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # This file is Copyright (c) 2010 by the GPSD project
5 # BSD terms apply: see the file COPYING in the distribution root for details.
6 #
7 # gps.py -- Python interface to GPSD.
8 #
9 # This interface has a lot of historical cruft in it related to old
10 # protocol, and was modeled on the C interface. It won't be thrown
11 # away, but it's likely to be deprecated in favor of something more
12 # Pythonic.
13 #
14 # The JSON parts of this (which will be reused by any new interface)
15 # now live in a different module.
16 #
17 import time
18 from client import *
19
20 NaN = float('nan')
21 def isnan(x): return str(x) == 'nan'
22
23 # Don't hand-hack this list, it's generated.
24 ONLINE_SET      = 0x00000001
25 TIME_SET        = 0x00000002
26 TIMERR_SET      = 0x00000004
27 LATLON_SET      = 0x00000008
28 ALTITUDE_SET    = 0x00000010
29 SPEED_SET       = 0x00000020
30 TRACK_SET       = 0x00000040
31 CLIMB_SET       = 0x00000080
32 STATUS_SET      = 0x00000100
33 MODE_SET        = 0x00000200
34 DOP_SET         = 0x00000400
35 VERSION_SET     = 0x00000800
36 HERR_SET        = 0x00001000
37 VERR_SET        = 0x00002000
38 ATTITUDE_SET    = 0x00004000
39 POLICY_SET      = 0x00008000
40 SATELLITE_SET   = 0x00010000
41 RAW_SET         = 0x00020000
42 USED_SET        = 0x00040000
43 SPEEDERR_SET    = 0x00080000
44 TRACKERR_SET    = 0x00100000
45 CLIMBERR_SET    = 0x00200000
46 DEVICE_SET      = 0x00400000
47 DEVICELIST_SET  = 0x00800000
48 DEVICEID_SET    = 0x01000000
49 ERROR_SET       = 0x02000000
50 RTCM2_SET       = 0x04000000
51 RTCM3_SET       = 0x08000000
52 AIS_SET         = 0x10000000
53 PACKET_SET      = 0x20000000
54 AUXDATA_SET     = 0x80000000
55 UNION_SET       = (RTCM2_SET|RTCM3_SET|AIS_SET|VERSION_SET|DEVICELIST_SET|ERROR_SET)
56
57 STATUS_NO_FIX = 0
58 STATUS_FIX = 1
59 STATUS_DGPS_FIX = 2
60 MODE_NO_FIX = 1
61 MODE_2D = 2
62 MODE_3D = 3
63 MAXCHANNELS = 20
64 SIGNAL_STRENGTH_UNKNOWN = NaN
65
66 WATCH_NEWSTYLE  = 0x00080
67 WATCH_OLDSTYLE  = 0x10000
68
69 class gpsfix:
70     def __init__(self):
71         self.mode = MODE_NO_FIX
72         self.time = NaN
73         self.ept = NaN
74         self.latitude = self.longitude = 0.0
75         self.epx = NaN
76         self.epy = NaN
77         self.altitude = NaN         # Meters
78         self.epv = NaN
79         self.track = NaN            # Degrees from true north
80         self.speed = NaN            # Knots
81         self.climb = NaN            # Meters per second
82         self.epd = NaN
83         self.eps = NaN
84         self.epc = NaN
85
86 class gpsdata:
87     "Position, track, velocity and status information returned by a GPS."
88
89     class satellite:
90         def __init__(self, PRN, elevation, azimuth, ss, used=None):
91             self.PRN = PRN
92             self.elevation = elevation
93             self.azimuth = azimuth
94             self.ss = ss
95             self.used = used
96         def __repr__(self):
97             return "PRN: %3d  E: %3d  Az: %3d  Ss: %3d  Used: %s" % (
98                 self.PRN, self.elevation, self.azimuth, self.ss, "ny"[self.used]
99             )
100
101     def __init__(self):
102         # Initialize all data members 
103         self.online = 0                 # NZ if GPS on, zero if not
104
105         self.valid = 0
106         self.fix = gpsfix()
107
108         self.status = STATUS_NO_FIX
109         self.utc = ""
110
111         self.satellites_used = 0        # Satellites used in last fix
112         self.xdop = self.ydop = self.vdop = self.tdop = 0
113         self.pdop = self.hdop = self.gdop = 0.0
114
115         self.epe = 0.0
116
117         self.satellites = []            # satellite objects in view
118
119         self.gps_id = None
120         self.driver_mode = 0
121         self.baudrate = 0
122         self.stopbits = 0
123         self.cycle = 0
124         self.mincycle = 0
125         self.device = None
126         self.devices = []
127
128         self.version = None
129         self.timings = None
130
131     def __repr__(self):
132         st = "Time:     %s (%s)\n" % (self.utc, self.fix.time)
133         st += "Lat/Lon:  %f %f\n" % (self.fix.latitude, self.fix.longitude)
134         if isnan(self.fix.altitude):
135             st += "Altitude: ?\n"
136         else:
137             st += "Altitude: %f\n" % (self.fix.altitude)
138         if isnan(self.fix.speed):
139             st += "Speed:    ?\n"
140         else:
141             st += "Speed:    %f\n" % (self.fix.speed)
142         if isnan(self.fix.track):
143             st += "Track:    ?\n"
144         else:
145             st += "Track:    %f\n" % (self.fix.track)
146         st += "Status:   STATUS_%s\n" % ("NO_FIX", "FIX", "DGPS_FIX")[self.status]
147         st += "Mode:     MODE_%s\n" % ("ZERO", "NO_FIX", "2D", "3D")[self.fix.mode]
148         st += "Quality:  %d p=%2.2f h=%2.2f v=%2.2f t=%2.2f g=%2.2f\n" % \
149               (self.satellites_used, self.pdop, self.hdop, self.vdop, self.tdop, self.gdop)
150         st += "Y: %s satellites in view:\n" % len(self.satellites)
151         for sat in self.satellites:
152           st += "    %r\n" % sat
153         return st
154
155 class gps(gpsdata, gpsjson):
156     "Client interface to a running gpsd instance."
157     def __init__(self, host="127.0.0.1", port=GPSD_PORT, verbose=0, mode=0):
158         gpscommon.__init__(self, host, port, verbose)
159         gpsdata.__init__(self)
160         self.raw_hook = None
161         self.newstyle = False
162         if mode:
163             self.stream(mode)
164
165     def set_raw_hook(self, hook):
166         self.raw_hook = hook
167
168     def __oldstyle_unpack(self, buf):
169         # unpack a daemon response into the gps instance members
170         self.fix.time = 0.0
171         fields = buf.strip().split(",")
172         if fields[0] == "GPSD":
173             for field in fields[1:]:
174                 if not field or field[1] != '=':
175                     continue
176                 cmd = field[0].upper()
177                 data = field[2:]
178                 if data[0] == "?":
179                     continue
180                 if cmd == 'F':
181                     self.device = data
182                 elif cmd == 'I':
183                     self.gps_id = data
184                 elif cmd == 'O':
185                     fields = data.split()
186                     if fields[0] == '?':
187                         self.fix.mode = MODE_NO_FIX
188                     else:
189                         def default(i, vbit=0, cnv=float):
190                             if fields[i] == '?':
191                                 return NaN
192                             else:
193                                 try:
194                                     value = cnv(fields[i])
195                                 except ValueError:
196                                     return NaN
197                                 self.valid |= vbit
198                                 return value
199                         # clear all valid bits that might be set again below
200                         self.valid &= ~(
201                             TIME_SET | TIMERR_SET | LATLON_SET | ALTITUDE_SET |
202                             HERR_SET | VERR_SET | TRACK_SET | SPEED_SET |
203                             CLIMB_SET | SPEEDERR_SET | CLIMBERR_SET | MODE_SET
204                         )
205                         self.utc = fields[1]
206                         self.fix.time = default(1, TIME_SET)
207                         if not isnan(self.fix.time):
208                             self.utc = isotime(self.fix.time)
209                         self.fix.ept = default(2, TIMERR_SET)
210                         self.fix.latitude = default(3, LATLON_SET)
211                         self.fix.longitude = default(4)
212                         self.fix.altitude = default(5, ALTITUDE_SET)
213                         self.fix.epx = self.epy = default(6, HERR_SET)
214                         self.fix.epv = default(7, VERR_SET)
215                         self.fix.track = default(8, TRACK_SET)
216                         self.fix.speed = default(9, SPEED_SET)
217                         self.fix.climb = default(10, CLIMB_SET)
218                         self.fix.epd = default(11)
219                         self.fix.eps = default(12, SPEEDERR_SET)
220                         self.fix.epc = default(13, CLIMBERR_SET)
221                         if len(fields) > 14:
222                             self.fix.mode = default(14, MODE_SET, int)
223                         else:
224                             if self.valid & ALTITUDE_SET:
225                                 self.fix.mode = MODE_2D
226                             else:
227                                 self.fix.mode = MODE_3D
228                             self.valid |= MODE_SET
229                 elif cmd == 'X':
230                     self.online = float(data)
231                     self.valid |= ONLINE_SET
232                 elif cmd == 'Y':
233                     satellites = data.split(":")
234                     prefix = satellites.pop(0).split()
235                     d1 = int(prefix.pop())
236                     newsats = []
237                     for i in range(d1):
238                         newsats.append(gps.satellite(*map(int, satellites[i].split())))
239                     self.satellites = newsats
240                     self.valid |= SATELLITE_SET
241
242     def __oldstyle_shim(self):
243         # The rest is backwards compatibility for the old interface
244         def default(k, dflt, vbit=0):
245             if k not in self.data.keys():
246                 return dflt
247             else:
248                 self.valid |= vbit
249                 return self.data[k]
250         if self.data.get("class") == "VERSION":
251             self.version = self.data
252         elif self.data.get("class") == "DEVICE":
253             self.valid = ONLINE_SET | DEVICE_SET
254             self.path        = self.data["path"]
255             self.activated   = default("activated", None)
256             driver = default("driver", None, DEVICEID_SET) 
257             subtype = default("subtype", None, DEVICEID_SET) 
258             self.gps_id      = driver
259             if subtype:
260                 self.gps_id += " " + subtype
261             self.driver_mode = default("native", 0)
262             self.baudrate    = default("bps", 0)
263             self.serialmode  = default("serialmode", "8N1")
264             self.cycle       = default("cycle",    NaN)
265             self.mincycle    = default("mincycle", NaN)
266         elif self.data.get("class") == "TPV":
267             self.valid = ONLINE_SET
268             self.fix.time = default("time", NaN, TIME_SET)
269             self.fix.ept =       default("ept",   NaN, TIMERR_SET)
270             self.fix.latitude =  default("lat",   NaN, LATLON_SET)
271             self.fix.longitude = default("lon",   NaN)
272             self.fix.altitude =  default("alt",   NaN, ALTITUDE_SET)
273             self.fix.epx =       default("epx",   NaN, HERR_SET)
274             self.fix.epy =       default("epy",   NaN, HERR_SET)
275             self.fix.epv =       default("epv",   NaN, VERR_SET)
276             self.fix.track =     default("track", NaN, TRACK_SET)
277             self.fix.speed =     default("speed", NaN, SPEED_SET)
278             self.fix.climb =     default("climb", NaN, CLIMB_SET)
279             self.fix.epd =       default("epd",   NaN)
280             self.fix.eps =       default("eps",   NaN, SPEEDERR_SET)
281             self.fix.epc =       default("epc",   NaN, CLIMBERR_SET)
282             self.fix.mode =      default("mode",  0,   MODE_SET)
283         elif self.data.get("class") == "SKY":
284             for attrp in "xyvhpg":
285                 setattr(self, attrp+"dop", default(attrp+"dop", NaN, DOP_SET))
286             if "satellites" in self.data.keys():
287                 self.satellites = [] 
288                 for sat in self.data['satellites']:
289                     self.satellites.append(gps.satellite(PRN=sat['PRN'], elevation=sat['el'], azimuth=sat['az'], ss=sat['ss'], used=sat['used']))
290             self.satellites_used = 0
291             for sat in self.satellites:
292                 if sat.used:
293                     self.satellites_used += 1
294             self.valid = ONLINE_SET | SATELLITE_SET
295         elif self.data.get("class") == "TIMING":
296             self.data["c_recv"] = self.received
297             self.data["c_decode"] = time.time()
298             self.timings = self.data
299
300     def poll(self):
301         "Read and interpret data from the daemon."
302         status = gpscommon.read(self)
303         if status <= 0:
304             return status
305         if self.raw_hook:
306             self.raw_hook(self.response);
307         if self.response.startswith("{") and self.response.endswith("}\r\n"):
308             self.json_unpack(self.response)
309             self.__oldstyle_shim()
310             self.newstyle = True
311             self.valid |= PACKET_SET
312         elif self.response.startswith("GPSD"):
313             self.__oldstyle_unpack(self.response)
314             self.valid |= PACKET_SET
315         return 0
316
317     def next(self):
318         if self.poll() == -1:
319             raise StopIteration
320         if hasattr(self, "data"):
321             return self.data
322         else:
323             return self.response
324
325     def stream(self, flags=0, outfile=None):
326         "Ask gpsd to stream reports at your client."
327         if (flags & (WATCH_JSON|WATCH_OLDSTYLE|WATCH_NMEA|WATCH_RAW)) == 0:
328             # If we're looking at a daemon that speaks JSON, this
329             # should have been set when we saw the initial VERSION
330             # response.  Note, however, that this requires at
331             # least one poll() before stream() is called
332             if self.newstyle or flags & WATCH_NEWSTYLE:
333                 flags |= WATCH_JSON
334             else:
335                 flags |= WATCH_OLDSTYLE
336         if flags & WATCH_OLDSTYLE:
337             if flags & WATCH_DISABLE:
338                 arg = "w-"
339                 if flags & WATCH_NMEA:
340                     arg += 'r-'
341                     return self.send(arg)
342             else: # flags & WATCH_ENABLE:
343                 arg = 'w+'
344                 if self.raw_hook or (flags & WATCH_NMEA):
345                     arg += 'r+'
346                     return self.send(arg)
347         else: # flags & WATCH_NEWSTYLE:
348             gpsjson.stream(self, flags)
349
350 if __name__ == '__main__':
351     import readline, getopt, sys
352     (options, arguments) = getopt.getopt(sys.argv[1:], "v")
353     streaming = False
354     verbose = False
355     for (switch, val) in options:
356         if switch == '-v':
357             verbose = True
358     if len(arguments) > 2:
359         print 'Usage: gps.py [-v] [host [port]]'
360         sys.exit(1)
361
362     opts = { "verbose" : verbose }
363     if len(arguments) > 0:
364         opts["host"] = arguments[0]
365     if len(arguments) > 1:
366         opts["port"] = arguments[1]
367
368     session = gps(**opts)
369     session.set_raw_hook(lambda s: sys.stdout.write(s.strip() + "\n"))
370     session.stream(WATCH_ENABLE|WATCH_NEWSTYLE)
371     for report in session:
372         print report
373
374 # gps.py ends here