"Initial commit to Gerrit"
[profile/ivi/gpsd.git] / gps / client.py
1 # This file is Copyright (c) 2010 by the GPSD project
2 # BSD terms apply: see the file COPYING in the distribution root for details.
3 #
4 import time, socket, sys, select
5
6 if sys.hexversion >= 0x2060000:
7     import json                 # For Python 2.6
8 else:
9     import simplejson as json   # For Python 2.4 and 2.5
10
11 GPSD_PORT="2947"
12
13 class gpscommon:
14     "Isolate socket handling and buffering from the protcol interpretation."
15     def __init__(self, host="127.0.0.1", port=GPSD_PORT, verbose=0):
16         self.sock = None        # in case we blow up in connect
17         self.linebuffer = ""
18         self.verbose = verbose
19         self.connect(host, port)
20
21     def connect(self, host, port):
22         """Connect to a host on a given port.
23
24         If the hostname ends with a colon (`:') followed by a number, and
25         there is no port specified, that suffix will be stripped off and the
26         number interpreted as the port number to use.
27         """
28         if not port and (host.find(':') == host.rfind(':')):
29             i = host.rfind(':')
30             if i >= 0:
31                 host, port = host[:i], host[i+1:]
32             try: port = int(port)
33             except ValueError:
34                 raise socket.error, "nonnumeric port"
35         #if self.verbose > 0:
36         #    print 'connect:', (host, port)
37         msg = "getaddrinfo returns an empty list"
38         self.sock = None
39         for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
40             af, socktype, proto, canonname, sa = res
41             try:
42                 self.sock = socket.socket(af, socktype, proto)
43                 #if self.debuglevel > 0: print 'connect:', (host, port)
44                 self.sock.connect(sa)
45             except socket.error, msg:
46                 #if self.debuglevel > 0: print 'connect fail:', (host, port)
47                 self.close()
48                 continue
49             break
50         if not self.sock:
51             raise socket.error, msg
52
53     def close(self):
54         if self.sock:
55             self.sock.close()
56         self.sock = None
57
58     def __del__(self):
59         self.close()
60
61     def waiting(self):
62         "Return True if data is ready for the client."
63         if self.linebuffer:
64             return True
65         (winput, woutput, wexceptions) = select.select((self.sock,), (), (), 0)
66         return winput != []
67
68     def read(self):
69         "Wait for and read data being streamed from the daemon."
70         if self.verbose > 1:
71             sys.stderr.write("poll: reading from daemon...\n")
72         eol = self.linebuffer.find('\n')
73         if eol == -1:
74             frag = self.sock.recv(4096)
75             self.linebuffer += frag
76             if self.verbose > 1:
77                 sys.stderr.write("poll: read complete.\n")
78             if not self.linebuffer:
79                 if self.verbose > 1:
80                     sys.stderr.write("poll: returning -1.\n")
81                 # Read failed
82                 return -1
83             eol = self.linebuffer.find('\n')
84             if eol == -1:
85                 if self.verbose > 1:
86                     sys.stderr.write("poll: returning 0.\n")
87                 # Read succeeded, but only got a fragment
88                 return 0
89         else:
90             if self.verbose > 1:
91                 sys.stderr.write("poll: fetching from buffer.\n")
92
93         # We got a line
94         eol += 1
95         self.response = self.linebuffer[:eol]
96         self.linebuffer = self.linebuffer[eol:]
97
98         # Can happen if daemon terminates while we're reading.
99         if not self.response:
100             return -1
101         if self.verbose:
102             sys.stderr.write("poll: data is %s\n" % repr(self.response))
103         self.received = time.time()
104         # We got a \n-terminated line
105         return len(self.response)
106
107     def send(self, commands):
108         "Ship commands to the daemon."
109         if not commands.endswith("\n"):
110             commands += "\n"
111         self.sock.send(commands)
112
113 WATCH_DISABLE   = 0x0000
114 WATCH_ENABLE    = 0x0001
115 WATCH_JSON      = 0x0002
116 WATCH_NMEA      = 0x0004
117 WATCH_RARE      = 0x0008
118 WATCH_RAW       = 0x0010
119 WATCH_SCALED    = 0x0020
120 WATCH_DEVICE    = 0x0040
121
122 class gpsjson(gpscommon):
123     "Basic JSON decoding."
124     def __iter__(self):
125         return self
126
127     def json_unpack(self, buf):
128         def asciify(d):
129             "De-Unicodify everything so we can copy dicts into Python objects."
130             t = {}
131             for (k, v) in d.items():
132                 ka = k.encode("ascii")
133                 if type(v) == type(u"x"):
134                     va = v.encode("ascii")
135                 elif type(v) == type({}):
136                     va = asciify(v)
137                 elif type(v) == type([]):
138                     va = map(asciify, v)
139                 else:
140                     va = v
141                 t[ka] = va
142             return t
143         self.data = dictwrapper(**asciify(json.loads(buf.strip(), encoding="ascii")))
144         # Should be done for any other array-valued subobjects, too.
145         if self.data["class"] == "SKY" and hasattr(self.data, "satellites"):
146             self.data.satellites = map(lambda x: dictwrapper(**x), self.data.satellites)
147
148     def stream(self, flags=0, outfile=None):
149         "Control streaming reports from the daemon,"
150         if flags & WATCH_DISABLE:
151             arg = '?WATCH={"enable":false'
152             if flags & WATCH_JSON:
153                 arg += ',"json":false'
154             if flags & WATCH_NMEA:
155                 arg += ',"nmea":false'
156             if flags & WATCH_RARE:
157                 arg += ',"raw":1'
158             if flags & WATCH_RAW:
159                 arg += ',"raw":2'
160             if flags & WATCH_SCALED:
161                 arg += ',"scaled":false'
162         else: # flags & WATCH_ENABLE:
163             arg = '?WATCH={"enable":true'
164             if flags & WATCH_JSON:
165                 arg += ',"json":true'
166             if flags & WATCH_NMEA:
167                 arg += ',"nmea":true'
168             if flags & WATCH_RAW:
169                 arg += ',"raw":1'
170             if flags & WATCH_RARE:
171                 arg += ',"raw":0'
172             if flags & WATCH_SCALED:
173                 arg += ',"scaled":true'
174             if flags & WATCH_DEVICE:
175                 arg += ',"device":"%s"' % outfile
176         return self.send(arg + "}")
177
178 class dictwrapper:
179     "Wrapper that yields both class and dictionary behavior,"
180     def __init__(self, **ddict):
181         self.__dict__ = ddict
182     def get(self, k, d=None):
183         return self.__dict__.get(k, d)
184     def keys(self):
185         return self.__dict__.keys()
186     def __getitem__(self, key):
187         "Emulate dictionary, for new-style interface."
188         return self.__dict__[key]
189     def __setitem__(self, key, val):
190         "Emulate dictionary, for new-style interface."
191         self.__dict__[key] = val
192     def __contains__(self, key):
193         return key in self.__dict__
194     def __str__(self):
195         return "<dictwrapper: " + str(self.__dict__) + ">"
196     __repr__ = __str__
197
198 #
199 # Someday a cleaner Python iterface using this machiner will live here
200 #
201
202 # End